{"id":7431,"date":"2023-03-02T12:50:26","date_gmt":"2023-03-02T18:50:26","guid":{"rendered":"https:\/\/gregsowell.com\/?p=7431"},"modified":"2023-03-02T12:50:26","modified_gmt":"2023-03-02T18:50:26","slug":"set-cisco-nexus-interface-description-from-cdp-using-ansible-aap","status":"publish","type":"post","link":"https:\/\/gregsowell.com\/?p=7431","title":{"rendered":"Set Cisco Nexus Interface Description From CDP Using Ansible AAP"},"content":{"rendered":"<p><a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/03\/Untitled-1.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/03\/Untitled-1-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" class=\"aligncenter size-medium wp-image-7436\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/03\/Untitled-1-300x169.jpg 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/03\/Untitled-1-768x432.jpg 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/03\/Untitled-1-1024x576.jpg 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2023\/03\/Untitled-1.jpg 1280w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\nA customer recently asked about setting CDP neighbors as interface descriptions on their switches, which is actually a pretty cool idea.  I thought, though, that I don&#8217;t want just anything set&#8230;ya know, like I don&#8217;t care if the neighbor is an IP phone, what I really care about are other switches.  So I created a playbook that will find the CDP neighbors and set the interface description to that neighbor IF it is a switch.<\/p>\n<h2>Video Overview<\/h2>\n<p><iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/tETf8t3seWE\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n<h2>Playbooks<\/h2>\n<p>First, you can find all of my playbooks in my <a href=\"https:\/\/github.com\/gregsowell\/ansible-nexus-cdp-interface\">public git repo here<\/a>.<\/p>\n<p>So the playbook is actually broken into two parts.  One is the main playbook, and one is a task file used for looping.<\/p>\n<p>Main playbook(cisco-nxos-cdp-interface.yml):<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">---\r\n- name: set interface desc based on cdp neighbors\r\n  hosts: nexus9k1\r\n  gather_facts: false\r\n  vars:\r\n  tasks:\r\n  - name: Gather all legacy facts\r\n    cisco.nxos.nxos_facts:\r\n      gather_subset: all\r\n    register: gather_info\r\n\r\n#  - name: debug gather_info\r\n#    ansible.builtin.debug:\r\n#      var: gather_info\r\n\r\n#  - name: debug gather_info filtered\r\n#    ansible.builtin.debug:\r\n#      msg: &quot;{{ item.key }} - {{ item.value.mode | default(&#039;nothing&#039;) }}&quot;\r\n#    loop: &quot;{{ gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr(&#039;value.mode&#039;,&#039;defined&#039;) | selectattr(&#039;value.mode&#039;,&#039;search&#039;,&#039;trunk&#039;) }}&quot;\r\n\r\n  - name: gather cdp neighbors\r\n    nxos_command:\r\n      commands: &quot;show cdp neighbors | json&quot;\r\n    register: cdp_neighbors\r\n\r\n\r\n#  - name: debug cdp_neighbors crazy filter stuff\r\n##    when: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr(&#039;key&#039;,&#039;search&#039;,item.intf_id) | selectattr(&#039;value.mode&#039;,&#039;defined&#039;) | map(attribute=&#039;value.mode&#039;) | default(&#039;access&#039;) == &#039;trunk&#039;\r\n##    when: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr(&#039;key&#039;,&#039;search&#039;,item.intf_id) | selectattr(&#039;value.mode&#039;,&#039;defined&#039;) | map(attribute=&#039;value.mode&#039;) | default(&#039;access&#039;) == &quot;trunk&quot;\r\n#    when: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr(&#039;key&#039;,&#039;equalto&#039;,item.intf_id) | selectattr(&#039;value.mode&#039;,&#039;defined&#039;) | map(attribute=&#039;value.mode&#039;) | default(&#039;access&#039;) == &quot;trunk&quot;\r\n#    ansible.builtin.debug:\r\n##      var: cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info\r\n##      var: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr(&#039;key&#039;,&#039;search&#039;,item.intf_id) | selectattr(&#039;value.mode&#039;,&#039;defined&#039;) | map(attribute=&#039;value.mode&#039;) | default(&#039;access&#039;)\r\n#      msg: &quot;{{ device_id }} on {{ item.intf_id }}&quot;\r\n#    loop: &quot;{{ cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info }}&quot;\r\n\r\n  - name: loop through cdp neighbors and call the task file to further search for trunks\r\n    ansible.builtin.include_tasks:\r\n      file: cisco-nxos-cdp-interface-taskfile.yml\r\n    loop: &quot;{{ cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info }}&quot;\r\n    loop_control:\r\n      loop_var: cdp_loop<\/code><\/pre>\n<p>I&#8217;ll break it down into its pieces:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Gather all legacy facts\r\n    cisco.nxos.nxos_facts:\r\n      gather_subset: all\r\n    register: gather_info\r\n\r\n  - name: gather cdp neighbors\r\n    nxos_command:\r\n      commands: &quot;show cdp neighbors | json&quot;\r\n    register: cdp_neighbors<\/code><\/pre>\n<p>These first two tasks are all about information gathering.  On the very first task I&#8217;m using the nxos_facts module to gather information about the switch.  While it pulls a lot of info, what I&#8217;m really interested in here is how the interfaces are configured, and in particular if the interface mode is set to access or trunk.<br \/>\nThe second task issues a simple command via the nxos_command module &#8220;show cdp neighbors | json&#8221;.  Here I&#8217;m piping the output to json, which means it will be saved in my &#8220;cdp_neighbors&#8221; variable directly as key\/value pairs(which means it&#8217;s directly ready for me to reference without having to do any parsing).<\/p>\n<p>Here&#8217;s the last task in the main playbook:<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: loop through cdp neighbors and call the task file to further search for trunks\r\n    ansible.builtin.include_tasks:\r\n      file: cisco-nxos-cdp-interface-taskfile.yml\r\n    loop: &quot;{{ cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info }}&quot;\r\n    loop_control:\r\n      loop_var: cdp_loop<\/code><\/pre>\n<p>This task loops over the collected CDP neighbors, and for each one it will call the &#8220;cisco-nxos-cdp-interface-taskfile.yml&#8221; task file for further processing.  Just remember that each time we hit the taskfile I&#8217;ll be processing a single CDP neighbor.<\/p>\n<p>Now for the task file(cisco-nxos-cdp-interface-taskfile.yml):<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">- name: Update interface descpription when it&#039;s a trunk port using Merge\r\n  when: item == &quot;trunk&quot;\r\n  check_mode: true\r\n  cisco.nxos.nxos_interfaces:\r\n    config:\r\n    - name: &quot;{{ cdp_loop.intf_id }}&quot;\r\n      description: &quot;{{ cdp_loop.device_id }} on {{ cdp_loop.port_id }}&quot;\r\n  loop: &quot;{{ gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr(&#039;key&#039;,&#039;equalto&#039;,cdp_loop.intf_id) | selectattr(&#039;value.mode&#039;,&#039;defined&#039;) | map(attribute=&#039;value.mode&#039;) | default(&#039;access&#039;) }}&quot;<\/code><\/pre>\n<p>There is only a single task here, but I am doing some kungfu down in the loop section.  Also note that I have &#8220;check_mode: true&#8221; set; I&#8217;m doing this because it is a demo I want to run multiple times and show interesting results, so I have it set to check mode so it won&#8217;t actually make the change, rather just show what it would do(in production this wouldn&#8217;t be there).<br \/>\nThe loop section looks pretty wacky, and if you don&#8217;t fully understand it, <a href=\"https:\/\/gregsowell.com\/?p=7380\">have a look at this article where I explain how to do complex parsing<\/a>.  In short I&#8217;m: taking the gathered facts interfaces, converting them to a list, finding the key field that has the same interface name(meaning while it&#8217;s a loop, it will return only a single object), then I ensure that the mode is defined(either access or trunk), I then map that value(which means I return just that one piece of info), and last if the value is null I set it to &#8220;access&#8221;.  So really what I&#8217;m doing is just finding out if it&#8217;s a &#8220;trunk&#8221; port, and if it&#8217;s not I make sure it&#8217;s set to &#8220;access&#8221;.<br \/>\nAt the top of the task you can see there&#8217;s a conditional that only processes the task if the interface that the CDP neighbor lives in is actually a &#8220;trunk&#8221; port.<br \/>\nIf it is a trunk port, then the interface description is set to the ID of the remote device as well as the port it is connecting via.<\/p>\n<h2>Conclusion<\/h2>\n<p>This is a pretty simple example of using ansible for a task, but I tried to jazz it up a bit by adding the extra filtering to give you an idea of how you can extend simple tasks with more specificity.  I&#8217;d be curious to hear how you&#8217;d tweak or tune this to fit your environment.<\/p>\n<p>Thanks and happy automating!  <\/p>\n","protected":false},"excerpt":{"rendered":"<p>A customer recently asked about setting CDP neighbors as interface descriptions on their switches, which is actually a pretty cool idea. I thought, though,\u2026<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,16,8],"tags":[],"class_list":["post-7431","post","type-post","status-publish","format-standard","hentry","category-ansible","category-cisco","category-networking"],"_links":{"self":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7431","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7431"}],"version-history":[{"count":6,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7431\/revisions"}],"predecessor-version":[{"id":7438,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7431\/revisions\/7438"}],"wp:attachment":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7431"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7431"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7431"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}