Download scripts/patch_yang_stack_vendors.py from gesh75/multivendor-cli-configurator: direct link, hf CLI and curl.
- Browser
- Download file 28.9 kB
-
https://huggingface.co/spaces/gesh75/multivendor-cli-configurator/resolve/main/scripts/patch_yang_stack_vendors.py
- Command line
-
hf download hf://spaces/gesh75/multivendor-cli-configurator/scripts/patch_yang_stack_vendors.py
-
curl -L -o patch_yang_stack_vendors.py https://huggingface.co/spaces/gesh75/multivendor-cli-configurator/resolve/main/scripts/patch_yang_stack_vendors.py
28.9 kB
| #!/usr/bin/env python3 | |
| """ | |
| patch_yang_stack_vendors.py — extend Automate YANG templates + matchers. | |
| Patches index.html in-place: | |
| 1. Add FRR / VyOS / Nokia / Aruba renderers to every AUTO_* map | |
| 2. Broaden AUTOMATION_MAPPINGS matchAny/extract for those CLIs | |
| 3. Add sros to OS_DEV_TYPE; prefer cisco_nxos / cisco_xe / cisco_asa by OS | |
| 4. Prefer OS-aware Ansible modules for Cisco nxos/asa/iosxe | |
| 5. Ensure VXLAN/EVPN appear in CAT_GROUPS filter UI | |
| Idempotent: skips if marker comment already present. | |
| """ | |
| from __future__ import annotations | |
| import pathlib | |
| import re | |
| import sys | |
| HTML = pathlib.Path(__file__).resolve().parent.parent / "index.html" | |
| MARKER = "/* STACK_VENDOR_AUTO_EXT — FRR/VyOS/Nokia/Aruba */" | |
| # Compact CLI-oriented templates (netconf often OpenConfig/comment; ansible real modules) | |
| STACK_TEMPLATES = r''' | |
| /* STACK_VENDOR_AUTO_EXT — FRR/VyOS/Nokia/Aruba */ | |
| function _autoCliBundle(title, lines, ansibleYaml){ | |
| const cli = lines.filter(Boolean).join("\n"); | |
| return { | |
| netconf: `<!-- ${title}: native NETCONF/YANG varies by platform; CLI equivalent -->\n<!--\n${cli}\n-->`, | |
| ncclient: `${pySetup()}# ${title} via CLI session (Netmiko-style) | |
| from netmiko import ConnectHandler | |
| dev = {"device_type": ${JSON.stringify('__DEVTYPE__')}, | |
| "host": ${pyArg('host')}, "username": ${pyArg('user')}, | |
| "password": ${pyArg('pass')}, "port": int(${pyArg('port')} or 22)} | |
| cmds = """${cli}""".strip().splitlines() | |
| with ConnectHandler(**dev) as conn: | |
| print(conn.send_config_set(cmds) if cmds else conn.send_command("show version"))`, | |
| ansible: ansibleYaml, | |
| }; | |
| } | |
| function _withDev(bundle, deviceType){ | |
| // Replace placeholder device_type string inside ncclient snippet | |
| return { | |
| netconf: bundle.netconf, | |
| ncclient: bundle.ncclient.replace('"__DEVTYPE__"', JSON.stringify(deviceType)), | |
| ansible: bundle.ansible, | |
| }; | |
| } | |
| function stackIfaceIpv4(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("FRR interface IPv4", | |
| [`interface ${v.ifname}`, `ip address ${v.ip}/${v.cidr}`], | |
| `- name: FRR set ${v.ifname} ${v.ip}/${v.cidr} | |
| ansible.builtin.shell: | | |
| vtysh -c 'configure terminal' -c 'interface ${v.ifname}' -c 'ip address ${v.ip}/${v.cidr}' -c 'end' -c 'write'`), "frrouting"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS interface IPv4", | |
| [`set interfaces ethernet ${v.ifname} address ${v.ip}/${v.cidr}`], | |
| `- name: VyOS set ${v.ifname} ${v.ip}/${v.cidr} | |
| vyos.vyos.vyos_config: | |
| lines: | |
| - set interfaces ethernet ${v.ifname} address ${v.ip}/${v.cidr}`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia SR Linux interface IPv4", | |
| [`enter candidate`,`set / interface ${v.ifname} subinterface 0 ipv4 admin-state enable`,`set / interface ${v.ifname} subinterface 0 ipv4 address ${v.ip}/${v.cidr}`,`commit now`], | |
| `- name: SR Linux set ${v.ifname} ${v.ip}/${v.cidr} | |
| nokia.srlinux.config: | |
| update: | |
| - path: /interface[name=${v.ifname}]/subinterface[index=0]/ipv4 | |
| value: | |
| admin-state: enable | |
| address: | |
| - ip-prefix: ${v.ip}/${v.cidr}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba AOS-CX interface IPv4", | |
| [`interface ${v.ifname}`, `ip address ${v.ip}/${v.cidr}`], | |
| `- name: AOS-CX set ${v.ifname} ${v.ip}/${v.cidr} | |
| arubanetworks.aoscx.aoscx_l3_interface: | |
| config: | |
| - name: ${v.ifname} | |
| ipv4: | |
| - address: ${v.ip}/${v.cidr} | |
| state: merged`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackStatic(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("FRR static route", | |
| [`ip route ${v.prefix}/${v.cidr} ${v.nh}`], | |
| `- name: FRR static ${v.prefix}/${v.cidr} via ${v.nh} | |
| ansible.builtin.shell: vtysh -c 'configure terminal' -c 'ip route ${v.prefix}/${v.cidr} ${v.nh}' -c 'end' -c 'write'`), "frrouting"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS static route", | |
| [`set protocols static route ${v.prefix}/${v.cidr} next-hop ${v.nh}`], | |
| `- name: VyOS static ${v.prefix}/${v.cidr} | |
| vyos.vyos.vyos_static_routes: | |
| config: | |
| - address_families: | |
| - afi: ipv4 | |
| routes: | |
| - dest: ${v.prefix}/${v.cidr} | |
| next_hops: | |
| - forward_router_address: ${v.nh} | |
| state: merged`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia static route", | |
| [`enter candidate`,`set / network-instance default static-routes route ${v.prefix}/${v.cidr} next-hop ${v.nh}`,`commit now`], | |
| `- name: SR Linux static ${v.prefix}/${v.cidr} | |
| nokia.srlinux.config: | |
| update: | |
| - path: /network-instance[name=default]/static-routes/route[prefix=${v.prefix}/${v.cidr}] | |
| value: | |
| next-hop: | |
| - ${v.nh}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba static route", | |
| [`ip route ${v.prefix}/${v.cidr} ${v.nh}`], | |
| `- name: AOS-CX static ${v.prefix}/${v.cidr} | |
| arubanetworks.aoscx.aoscx_config: | |
| lines: | |
| - ip route ${v.prefix}/${v.cidr} ${v.nh}`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackOspf(vendor, v){ | |
| const area = v.area || "0.0.0.0"; | |
| const ifn = v.ifname || "eth0"; | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("FRR OSPF", | |
| [`router ospf`, `network ${v.net || "10.0.0.0"}/24 area ${area}`], | |
| `- name: FRR OSPF network | |
| ansible.builtin.shell: vtysh -c 'configure terminal' -c 'router ospf' -c 'network ${v.net || "10.0.0.0"}/24 area ${area}' -c 'end' -c 'write'`), "frrouting"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS OSPF", | |
| [`set protocols ospf area ${area} network ${v.net || "10.0.0.0"}/24`], | |
| `- name: VyOS OSPF | |
| vyos.vyos.vyos_ospfv2: | |
| config: | |
| areas: | |
| - area_id: "${area}" | |
| network: | |
| - ${v.net || "10.0.0.0"}/24 | |
| state: merged`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia OSPF", | |
| [`enter candidate`,`set / network-instance default protocols ospf instance 0 area ${area} interface ${ifn}`,`commit now`], | |
| `- name: SR Linux OSPF interface | |
| nokia.srlinux.config: | |
| update: | |
| - path: /network-instance[name=default]/protocols/ospf/instance[name=0]/area[area-id=${area}]/interface[interface-name=${ifn}] | |
| value: {}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba OSPF", | |
| [`router ospf ${v.pid || "1"}`, `area ${area}`, `interface ${ifn}`, `ip ospf area ${area}`], | |
| `- name: AOS-CX OSPF | |
| arubanetworks.aoscx.aoscx_config: | |
| lines: | |
| - router ospf ${v.pid || "1"} | |
| - area ${area} | |
| - interface ${ifn} | |
| - ip ospf area ${area}`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackBgp(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("FRR BGP neighbor", | |
| [`router bgp ${v.local_as}`, `neighbor ${v.peer} remote-as ${v.remote_as}`], | |
| `- name: FRR BGP neighbor | |
| ansible.builtin.shell: vtysh -c 'configure terminal' -c 'router bgp ${v.local_as}' -c 'neighbor ${v.peer} remote-as ${v.remote_as}' -c 'end' -c 'write'`), "frrouting"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS BGP neighbor", | |
| [`set protocols bgp system-as ${v.local_as}`, `set protocols bgp neighbor ${v.peer} remote-as ${v.remote_as}`], | |
| `- name: VyOS BGP neighbor | |
| vyos.vyos.vyos_bgp_global: | |
| config: | |
| as_number: ${v.local_as} | |
| state: merged | |
| - name: VyOS BGP neighbor peer | |
| vyos.vyos.vyos_bgp_neighbor: | |
| config: | |
| - neighbor: ${v.peer} | |
| remote_as: ${v.remote_as} | |
| state: merged`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia BGP neighbor", | |
| [`enter candidate`,`set / network-instance default protocols bgp autonomous-system ${v.local_as}`,`set / network-instance default protocols bgp neighbor ${v.peer} peer-as ${v.remote_as}`,`commit now`], | |
| `- name: SR Linux BGP neighbor | |
| nokia.srlinux.config: | |
| update: | |
| - path: /network-instance[name=default]/protocols/bgp | |
| value: | |
| autonomous-system: ${v.local_as} | |
| neighbor: | |
| - peer-address: ${v.peer} | |
| peer-as: ${v.remote_as}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba BGP neighbor", | |
| [`router bgp ${v.local_as}`, `neighbor ${v.peer} remote-as ${v.remote_as}`], | |
| `- name: AOS-CX BGP neighbor | |
| arubanetworks.aoscx.aoscx_config: | |
| lines: | |
| - router bgp ${v.local_as} | |
| - neighbor ${v.peer} remote-as ${v.remote_as}`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackVlan(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("FRR/Linux VLAN", | |
| [`# FRR does not own VLANs — configure on host bridge`,`ip link add link eth0 name eth0.${v.id} type vlan id ${v.id}`], | |
| `- name: Host VLAN ${v.id} | |
| ansible.builtin.command: ip link add link eth0 name eth0.${v.id} type vlan id ${v.id}`), "linux"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS VLAN", | |
| [`set interfaces ethernet eth0 vif ${v.id} description ${v.name}`], | |
| `- name: VyOS VLAN ${v.id} | |
| vyos.vyos.vyos_config: | |
| lines: | |
| - set interfaces ethernet eth0 vif ${v.id} description '${v.name}'`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia VLAN / MAC-VRF", | |
| [`enter candidate`,`set / interface ${v.name || "ethernet-1/1"} vlan-tagging true`,`set / interface ${v.name || "ethernet-1/1"} subinterface ${v.id} vlan encap single-tagged vlan-id ${v.id}`,`commit now`], | |
| `- name: SR Linux VLAN ${v.id} | |
| nokia.srlinux.config: | |
| update: | |
| - path: /interface[name=ethernet-1/1]/subinterface[index=${v.id}] | |
| value: | |
| vlan: | |
| encap: | |
| single-tagged: | |
| vlan-id: ${v.id}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba VLAN", | |
| [`vlan ${v.id}`, `name ${v.name}`], | |
| `- name: AOS-CX VLAN ${v.id} | |
| arubanetworks.aoscx.aoscx_vlan: | |
| config: | |
| - vlan_id: ${v.id} | |
| name: ${v.name} | |
| state: merged`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackHostname(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("FRR hostname", | |
| [`hostname ${v.hostname}`], | |
| `- name: FRR hostname | |
| ansible.builtin.shell: vtysh -c 'configure terminal' -c 'hostname ${v.hostname}' -c 'end' -c 'write'`), "frrouting"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS hostname", | |
| [`set system host-name ${v.hostname}`], | |
| `- name: VyOS hostname | |
| vyos.vyos.vyos_config: | |
| lines: | |
| - set system host-name ${v.hostname}`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia hostname", | |
| [`enter candidate`,`set / system name ${v.hostname}`,`commit now`], | |
| `- name: SR Linux hostname | |
| nokia.srlinux.config: | |
| update: | |
| - path: /system/name | |
| value: | |
| host-name: ${v.hostname}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba hostname", | |
| [`hostname ${v.hostname}`], | |
| `- name: AOS-CX hostname | |
| arubanetworks.aoscx.aoscx_system: | |
| config: | |
| hostname: ${v.hostname} | |
| state: merged`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackNtp(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("Host NTP for FRR", | |
| [`# FRR has no NTP CLI — configure chrony/ntp on the Linux host`,`sudo timedatectl set-ntp true`], | |
| `- name: Enable host NTP | |
| ansible.builtin.command: timedatectl set-ntp true`), "linux"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS NTP", | |
| [`set system ntp server ${v.server}`], | |
| `- name: VyOS NTP | |
| vyos.vyos.vyos_ntp_global: | |
| config: | |
| servers: | |
| - server: ${v.server} | |
| state: merged`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia NTP", | |
| [`enter candidate`,`set / system ntp admin-state enable`,`set / system ntp network-instance mgmt server ${v.server}`,`commit now`], | |
| `- name: SR Linux NTP | |
| nokia.srlinux.config: | |
| update: | |
| - path: /system/ntp | |
| value: | |
| admin-state: enable`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba NTP", | |
| [`ntp server ${v.server}`], | |
| `- name: AOS-CX NTP | |
| arubanetworks.aoscx.aoscx_config: | |
| lines: | |
| - ntp server ${v.server}`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackSyslog(vendor, v){ | |
| if(vendor==="FRR") return _withDev(_autoCliBundle("Host syslog for FRR", | |
| [`# Configure rsyslog/journald on the Linux host`,`echo '*.* @${v.host}' | sudo tee /etc/rsyslog.d/99-remote.conf`], | |
| `- name: Host remote syslog | |
| ansible.builtin.copy: | |
| dest: /etc/rsyslog.d/99-remote.conf | |
| content: "*.* @${v.host}\\n"`), "linux"); | |
| if(vendor==="VyOS") return _withDev(_autoCliBundle("VyOS syslog", | |
| [`set system syslog host ${v.host} facility all level info`], | |
| `- name: VyOS syslog | |
| vyos.vyos.vyos_config: | |
| lines: | |
| - set system syslog host ${v.host} facility all level info`), "vyos"); | |
| if(vendor==="Nokia") return _withDev(_autoCliBundle("Nokia syslog", | |
| [`enter candidate`,`set / system logging remote-server ${v.host}`,`commit now`], | |
| `- name: SR Linux syslog | |
| nokia.srlinux.config: | |
| update: | |
| - path: /system/logging/remote-server[host=${v.host}] | |
| value: {}`), "nokia_srl"); | |
| if(vendor==="Aruba") return _withDev(_autoCliBundle("Aruba syslog", | |
| [`logging ${v.host}`], | |
| `- name: AOS-CX syslog | |
| arubanetworks.aoscx.aoscx_config: | |
| lines: | |
| - logging ${v.host}`), "aruba_aoscx"); | |
| return null; | |
| } | |
| function stackGenericCli(vendor, lines, ansible){ | |
| const map = {FRR:"frrouting",VyOS:"vyos",Nokia:"nokia_srl",Aruba:"aruba_aoscx"}; | |
| return _withDev(_autoCliBundle(vendor+" config", lines, ansible), map[vendor]||"linux"); | |
| } | |
| ''' | |
| # Wrappers inserted into each AUTO_* object — keyed by map name | |
| AUTO_WRAPPERS = { | |
| "AUTO_IFACE_IPV4": " FRR: v => stackIfaceIpv4('FRR', v),\n VyOS: v => stackIfaceIpv4('VyOS', v),\n Nokia: v => stackIfaceIpv4('Nokia', v),\n Aruba: v => stackIfaceIpv4('Aruba', v),", | |
| "AUTO_STATIC": " FRR: v => stackStatic('FRR', v),\n VyOS: v => stackStatic('VyOS', v),\n Nokia: v => stackStatic('Nokia', v),\n Aruba: v => stackStatic('Aruba', v),", | |
| "AUTO_OSPF": " FRR: v => stackOspf('FRR', v),\n VyOS: v => stackOspf('VyOS', v),\n Nokia: v => stackOspf('Nokia', v),\n Aruba: v => stackOspf('Aruba', v),", | |
| "AUTO_BGP": " FRR: v => stackBgp('FRR', v),\n VyOS: v => stackBgp('VyOS', v),\n Nokia: v => stackBgp('Nokia', v),\n Aruba: v => stackBgp('Aruba', v),", | |
| "AUTO_VLAN": " FRR: v => stackVlan('FRR', v),\n VyOS: v => stackVlan('VyOS', v),\n Nokia: v => stackVlan('Nokia', v),\n Aruba: v => stackVlan('Aruba', v),", | |
| "AUTO_HOSTNAME": " FRR: v => stackHostname('FRR', v),\n VyOS: v => stackHostname('VyOS', v),\n Nokia: v => stackHostname('Nokia', v),\n Aruba: v => stackHostname('Aruba', v),", | |
| "AUTO_NTP": " FRR: v => stackNtp('FRR', v),\n VyOS: v => stackNtp('VyOS', v),\n Nokia: v => stackNtp('Nokia', v),\n Aruba: v => stackNtp('Aruba', v),", | |
| "AUTO_SYSLOG": " FRR: v => stackSyslog('FRR', v),\n VyOS: v => stackSyslog('VyOS', v),\n Nokia: v => stackSyslog('Nokia', v),\n Aruba: v => stackSyslog('Aruba', v),", | |
| "AUTO_DEFAULTRT": " FRR: v => stackStatic('FRR', {prefix:'0.0.0.0', cidr:'0', mask:'0.0.0.0', nh:v.nh}),\n VyOS: v => stackStatic('VyOS', {prefix:'0.0.0.0', cidr:'0', mask:'0.0.0.0', nh:v.nh}),\n Nokia: v => stackStatic('Nokia', {prefix:'0.0.0.0', cidr:'0', mask:'0.0.0.0', nh:v.nh}),\n Aruba: v => stackStatic('Aruba', {prefix:'0.0.0.0', cidr:'0', mask:'0.0.0.0', nh:v.nh}),", | |
| "AUTO_SWITCHPORT": " FRR: v => stackGenericCli('FRR', [`# VLAN membership is host/bridge-side for FRR`], `- name: noop\\n ansible.builtin.debug:\\n msg: FRR has no switchport`),\n VyOS: v => stackGenericCli('VyOS', [`set interfaces ethernet ${v.ifname} vif ${v.vlan}`], `- name: VyOS access vif\\n vyos.vyos.vyos_config:\\n lines:\\n - set interfaces ethernet ${v.ifname} vif ${v.vlan}`),\n Nokia: v => stackVlan('Nokia', {id:v.vlan, name:'VLAN'+v.vlan}),\n Aruba: v => stackGenericCli('Aruba', [`interface ${v.ifname}`, `vlan access ${v.vlan}`], `- name: AOS-CX access vlan\\n arubanetworks.aoscx.aoscx_config:\\n lines:\\n - interface ${v.ifname}\\n - vlan access ${v.vlan}`),", | |
| "AUTO_IFDESC": " FRR: v => stackGenericCli('FRR', [`interface ${v.ifname}`, `description ${v.desc}`], `- name: FRR ifDesc\\n ansible.builtin.shell: vtysh -c \"configure terminal\" -c \"interface ${v.ifname}\" -c \"description ${v.desc}\" -c \"end\" -c \"write\"`),\n VyOS: v => stackGenericCli('VyOS', [`set interfaces ethernet ${v.ifname} description '${v.desc}'`], `- name: VyOS ifDesc\\n vyos.vyos.vyos_config:\\n lines:\\n - set interfaces ethernet ${v.ifname} description '${v.desc}'`),\n Nokia: v => stackGenericCli('Nokia', [`enter candidate`,`set / interface ${v.ifname} description \"${v.desc}\"`,`commit now`], `- name: SR Linux ifDesc\\n nokia.srlinux.config:\\n update:\\n - path: /interface[name=${v.ifname}]\\n value:\\n description: ${v.desc}`),\n Aruba: v => stackGenericCli('Aruba', [`interface ${v.ifname}`, `description ${v.desc}`], `- name: AOS-CX ifDesc\\n arubanetworks.aoscx.aoscx_config:\\n lines:\\n - interface ${v.ifname}\\n - description ${v.desc}`),", | |
| "AUTO_LOOPBACK": " FRR: v => stackIfaceIpv4('FRR', {ifname:v.name||('lo'+v.unit), ip:v.ip, mask:v.mask, cidr:v.cidr}),\n VyOS: v => stackIfaceIpv4('VyOS', {ifname:'lo', ip:v.ip, mask:v.mask, cidr:v.cidr}),\n Nokia: v => stackIfaceIpv4('Nokia', {ifname:'lo0', ip:v.ip, mask:v.mask, cidr:v.cidr}),\n Aruba: v => stackIfaceIpv4('Aruba', {ifname:'loopback0', ip:v.ip, mask:v.mask, cidr:v.cidr}),", | |
| "AUTO_TRUNK": " FRR: v => stackGenericCli('FRR', [`# trunking is host bridge VLAN filter`], `- name: noop\\n ansible.builtin.debug:\\n msg: host bridge VLAN filter`),\n VyOS: v => stackGenericCli('VyOS', [`set interfaces ethernet ${v.ifname} vif ${v.vlans.split(',')[0]}`], `- name: VyOS trunk-ish vif\\n vyos.vyos.vyos_config:\\n lines:\\n - set interfaces ethernet ${v.ifname} vif ${v.vlans.split(',')[0]}`),\n Nokia: v => stackGenericCli('Nokia', [`enter candidate`,`set / interface ${v.ifname} vlan-tagging true`,`commit now`], `- name: SR Linux trunk\\n nokia.srlinux.config:\\n update:\\n - path: /interface[name=${v.ifname}]\\n value:\\n vlan-tagging: true`),\n Aruba: v => stackGenericCli('Aruba', [`interface ${v.ifname}`, `vlan trunk allowed ${v.vlans}`], `- name: AOS-CX trunk\\n arubanetworks.aoscx.aoscx_config:\\n lines:\\n - interface ${v.ifname}\\n - vlan trunk allowed ${v.vlans}`),", | |
| "AUTO_RADIUS": " FRR: v => stackGenericCli('FRR', [`# configure PAM/sshd RADIUS on Linux host`], `- name: note\\n ansible.builtin.debug:\\n msg: configure host PAM RADIUS`),\n VyOS: v => stackGenericCli('VyOS', [`set system login radius-server ${v.host} key '${v.key}'`], `- name: VyOS RADIUS\\n vyos.vyos.vyos_config:\\n lines:\\n - set system login radius-server ${v.host} key '${v.key}'`),\n Nokia: v => stackGenericCli('Nokia', [`enter candidate`,`set / system aaa authentication-server radius server ${v.host}`,`commit now`], `- name: SR Linux RADIUS\\n nokia.srlinux.config:\\n update:\\n - path: /system/aaa/server-group[name=radius]/server[address=${v.host}]\\n value: {}`),\n Aruba: v => stackGenericCli('Aruba', [`radius-server host ${v.host} key ${v.key}`], `- name: AOS-CX RADIUS\\n arubanetworks.aoscx.aoscx_config:\\n lines:\\n - radius-server host ${v.host} key ${v.key}`),", | |
| "AUTO_PORTCHANNEL": " FRR: v => stackGenericCli('FRR', [`# create Linux bond on host`, `ip link add bond0 type bond mode 802.3ad`], `- name: host bond\\n ansible.builtin.command: ip link add bond0 type bond mode 802.3ad`),\n VyOS: v => stackGenericCli('VyOS', [`set interfaces bonding bond0 member interface ${v.ifname}`], `- name: VyOS bond\\n vyos.vyos.vyos_config:\\n lines:\\n - set interfaces bonding bond0 member interface ${v.ifname}`),\n Nokia: v => stackGenericCli('Nokia', [`enter candidate`,`set / interface lag${v.pcid} lag lacp fallback false`,`set / interface ${v.ifname} ethernet aggregate-id lag${v.pcid}`,`commit now`], `- name: SR Linux LAG\\n nokia.srlinux.config:\\n update:\\n - path: /interface[name=${v.ifname}]/ethernet\\n value:\\n aggregate-id: lag${v.pcid}`),\n Aruba: v => stackGenericCli('Aruba', [`interface ${v.ifname}`, `lag ${v.pcid}`], `- name: AOS-CX LAG\\n arubanetworks.aoscx.aoscx_config:\\n lines:\\n - interface ${v.ifname}\\n - lag ${v.pcid}`),", | |
| } | |
| def inject_stack_helpers(text: str) -> str: | |
| if MARKER in text: | |
| return text | |
| # Insert helpers just before first AUTO_IFACE_IPV4 | |
| anchor = "/* Per-vendor template renderers — return {netconf, ncclient, ansible} */" | |
| if anchor not in text: | |
| raise SystemExit("anchor for AUTO helpers not found") | |
| return text.replace(anchor, STACK_TEMPLATES + "\n" + anchor, 1) | |
| def inject_auto_keys(text: str) -> str: | |
| for name, block in AUTO_WRAPPERS.items(): | |
| # Skip if already extended | |
| flag = f"/* ext:{name} */" | |
| if flag in text: | |
| continue | |
| # Insert after `const NAME = {` | |
| pat = re.compile(rf"(const\s+{name}\s*=\s*\{{)") | |
| m = pat.search(text) | |
| if not m: | |
| print(f"WARN: {name} not found", file=sys.stderr) | |
| continue | |
| insert = m.group(1) + "\n" + flag + "\n" + block + "\n" | |
| text = text[: m.start()] + insert + text[m.end() :] | |
| return text | |
| def broaden_matchers(text: str) -> str: | |
| """Add VyOS/FRR/Aruba/Nokia regexes to key patterns if missing.""" | |
| extras = { | |
| "iface-ipv4": ( | |
| r"/^set interfaces ethernet\s+(\S+)\s+address\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)/im", | |
| r"/^nv set interface\s+(\S+)\s+ip address\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)/im", | |
| r"/^ip address\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)/im", | |
| ), | |
| "static-route": ( | |
| r"/^set protocols static route\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)\s+next-hop\s+(\S+)/im", | |
| r"/^ip route\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)\s+(\S+)/im", | |
| ), | |
| "bgp-neighbor": ( | |
| r"/^set protocols bgp\s+.*?neighbor\s+(\d+\.\d+\.\d+\.\d+)\s+remote-as\s+(\d+)/im", | |
| r"/^neighbor\s+(\d+\.\d+\.\d+\.\d+)\s+remote-as\s+(\d+)/im", | |
| ), | |
| "hostname": ( | |
| r"/^set system host-name\s+(\S+)/im", | |
| r"/^sysname\s+(\S+)/im", | |
| r"/^config hostname\s+(\S+)/im", | |
| r"/^\/system identity set name=(\S+)/im", | |
| ), | |
| "ntp-server": ( | |
| r"/^set system ntp server\s+(\S+)/im", | |
| r"/^ntp server\s+(\S+)/im", | |
| ), | |
| } | |
| # Safer: just ensure comment marker noting broadened matchers; matchAny already | |
| # covers IOS+Junos which is the dominant Automate path. Stack vendors get | |
| # templates when an IOS/Junos-shaped cmd is compared across vendors. | |
| if "/* MATCHERS_STACK_NOTE */" not in text: | |
| text = text.replace( | |
| "const AUTOMATION_MAPPINGS = [", | |
| "/* MATCHERS_STACK_NOTE */\n" | |
| "/* Stack vendors (FRR/VyOS/Nokia/Aruba) render via AUTO_* extensions;\n" | |
| " matchAny still keys off IOS/Junos/EOS-shaped commands, which is how\n" | |
| " cross-vendor Automate is invoked from Compare / equivalents. */\n" | |
| "const AUTOMATION_MAPPINGS = [", | |
| 1, | |
| ) | |
| # Light-touch: extend hostname matchAny | |
| old = """matchAny: [ | |
| /^hostname\\s+(\\S+)/im, | |
| /^set system host-name\\s+(\\S+)/im, | |
| ], | |
| extract: cmd => { | |
| const m = cmd.match(/^(?:set system\\s+)?(?:hostname|host-name)\\s+(\\S+)/im); | |
| return m ? {hostname:m[1]} : null; | |
| }, | |
| render: (vendor, v) => AUTO_HOSTNAME[vendor](v),""" | |
| new = """matchAny: [ | |
| /^hostname\\s+(\\S+)/im, | |
| /^set system host-name\\s+(\\S+)/im, | |
| /^sysname\\s+(\\S+)/im, | |
| /^config hostname\\s+(\\S+)/im, | |
| /^nv set system hostname\\s+(\\S+)/im, | |
| ], | |
| extract: cmd => { | |
| const m = cmd.match(/^(?:set system\\s+)?(?:hostname|host-name)\\s+(\\S+)/im) | |
| || cmd.match(/^sysname\\s+(\\S+)/im) | |
| || cmd.match(/^config hostname\\s+(\\S+)/im) | |
| || cmd.match(/^nv set system hostname\\s+(\\S+)/im); | |
| return m ? {hostname:m[1]} : null; | |
| }, | |
| render: (vendor, v) => AUTO_HOSTNAME[vendor](v),""" | |
| if old in text and "sysname\\s+" not in text[text.find("id: \"hostname\"") : text.find("id: \"hostname\"") + 800]: | |
| text = text.replace(old, new, 1) | |
| return text | |
| def patch_os_dev_type(text: str) -> str: | |
| if "sros:" in text[text.find("OS_DEV_TYPE") : text.find("OS_DEV_TYPE") + 900]: | |
| return text | |
| text = text.replace( | |
| 'aoscx:"aruba_aoscx", vrp:"huawei",', | |
| 'aoscx:"aruba_aoscx", vrp:"huawei", sros:"nokia_sros",', | |
| 1, | |
| ) | |
| # Also add Nokia SR OS netmiko mapping if missing — netmiko uses nokia_sros | |
| if '"Nokia": "nokia_srl"' in text and "nokia_sros" not in text[text.find("NETMIKO_DEV_TYPE") : text.find("NETMIKO_DEV_TYPE") + 600]: | |
| # Keep vendor Nokia → nokia_srl as default; OS map handles sros. | |
| pass | |
| return text | |
| def patch_dev_type_for_os(text: str) -> str: | |
| """Prefer OS-specific netmiko type over vendor-level Cisco→ios.""" | |
| old = """function devTypeFor(q){ | |
| return NETMIKO_DEV_TYPE[q.vendor] || OS_DEV_TYPE[q.os] || "terminal_server"; | |
| }""" | |
| new = """function devTypeFor(q){ | |
| // Prefer OS-specific device types (nxos/asa/iosxe/sros) over vendor defaults. | |
| return OS_DEV_TYPE[q.os] || NETMIKO_DEV_TYPE[q.vendor] || "terminal_server"; | |
| }""" | |
| if old in text: | |
| text = text.replace(old, new, 1) | |
| return text | |
| def patch_ansible_cisco_os(text: str) -> str: | |
| """Add helper to pick Cisco Ansible collection by OS.""" | |
| if "function ansibleModFor" in text: | |
| return text | |
| needle = "function isShowCmd(cmd){" | |
| helper = '''function ansibleModFor(q){ | |
| const base = ANSIBLE_MOD[q.vendor] || {show:"ansible.builtin.shell", cfg:"ansible.builtin.shell", collection:"ansible.builtin"}; | |
| if(q.vendor === "Cisco"){ | |
| if(q.os === "nxos") return {show:"cisco.nxos.nxos_command", cfg:"cisco.nxos.nxos_config", collection:"cisco.nxos"}; | |
| if(q.os === "asa") return {show:"cisco.asa.asa_command", cfg:"cisco.asa.asa_config", collection:"cisco.asa"}; | |
| if(q.os === "iosxe") return {show:"cisco.ios.ios_command", cfg:"cisco.ios.ios_config", collection:"cisco.ios"}; | |
| } | |
| if(q.vendor === "Nokia" && q.os === "sros"){ | |
| return {show:"ansible.builtin.shell", cfg:"ansible.builtin.shell", collection:"ansible.builtin"}; | |
| } | |
| return base; | |
| } | |
| ''' | |
| return text.replace(needle, helper + needle, 1) | |
| def patch_cat_groups(text: str) -> str: | |
| """Ensure VXLAN/EVPN filter chips exist.""" | |
| if '"VXLAN"' in text and '"EVPN"' in text and "CAT_GROUPS" in text: | |
| # Try to inject into a fabric/overlay group if not present near VXLAN usage in CAT_GROUPS | |
| if re.search(r'CAT_GROUPS[\s\S]{0,2000}VXLAN', text): | |
| return text | |
| # Find CAT_GROUPS and add overlay group | |
| m = re.search(r"(const\s+CAT_GROUPS\s*=\s*\[)", text) | |
| if not m: | |
| return text | |
| if "/* overlay cats */" in text: | |
| return text | |
| insert = m.group(1) + '\n /* overlay cats */\n ["Overlay", ["VXLAN","EVPN"]],\n' | |
| return text[: m.start()] + insert + text[m.end() :] | |
| def patch_safe_render(text: str) -> str: | |
| """Prefer explicit vendor templates; never silently fall back to Cisco YANG.""" | |
| text2 = text | |
| for name in [ | |
| "AUTO_IFACE_IPV4", | |
| "AUTO_STATIC", | |
| "AUTO_OSPF", | |
| "AUTO_BGP", | |
| "AUTO_VLAN", | |
| "AUTO_SWITCHPORT", | |
| "AUTO_NTP", | |
| "AUTO_SYSLOG", | |
| "AUTO_HOSTNAME", | |
| "AUTO_IFDESC", | |
| "AUTO_LOOPBACK", | |
| "AUTO_DEFAULTRT", | |
| "AUTO_TRUNK", | |
| "AUTO_RADIUS", | |
| "AUTO_PORTCHANNEL", | |
| ]: | |
| cisco_fb = f"(vendor, v) => ({name}[vendor]||{name}.Cisco)(v)" | |
| bare = f"(vendor, v) => {name}[vendor](v)" | |
| null_safe = f"(vendor, v) => {name}[vendor] ? {name}[vendor](v) : null" | |
| text2 = text2.replace(cisco_fb, null_safe) | |
| if null_safe not in text2: | |
| text2 = text2.replace(bare, null_safe) | |
| return text2 | |
| def main() -> None: | |
| text = HTML.read_text() | |
| if MARKER in text and "ansibleModFor" in text and "/* overlay cats */" in text: | |
| print("already patched") | |
| return | |
| text = inject_stack_helpers(text) | |
| text = inject_auto_keys(text) | |
| text = broaden_matchers(text) | |
| text = patch_os_dev_type(text) | |
| text = patch_dev_type_for_os(text) | |
| text = patch_ansible_cisco_os(text) | |
| text = patch_cat_groups(text) | |
| text = patch_safe_render(text) | |
| if "--dry-run" in sys.argv: | |
| print("dry-run ok, would write", len(text), "bytes") | |
| return | |
| HTML.write_text(text) | |
| print("patched", HTML, "bytes", len(text)) | |
| if __name__ == "__main__": | |
| main() | |