Hi Phil,
For FRR we have been using the core Ansible template module to manage the frr.conf configuration file. Then to run an individual command I use the core command or shell modules. The reason for this is mostly so we don't have to write any sort of custom API's or wrappers to configure FRR. However you're idea is inline with many of the other vendors who have implemented a config via cli module and command module for their platforms. Cumulus put out a module with Ansible 2.3 using NCLU:
http://docs.ansible.com/ansible/nclu_module.html which you can use to configure FRR and for running arbitrary commands on FRR and Linux OS in general. To be fair NCLU is mostly used in a Cumulus specific environment but if you want to do a command/config module I would take a look at that one. It kind of gives you the best of both worlds, CLI for running commands and idempotency using a templated file.
There are pros and cons to using a command CLI model versus a template model I would be happy to discuss and hear your thoughts it's something we've been going through a lot internally and with customers. I can see the value of a command module to "wrapper" the vtysh -c stuff and potentially eliminate the pain of order of operations.
Here are some examples of what we've been doing to maybe generate some ideas:
Example of an adhoc command:
ansible -a "vtysh -c 'show ip route'" leaf01
Example for Ansible variable and template for configuring BGP (Since you mentioned you wanted to write modules I'll drop the code example here):
Routing
v
ariables:
bgp:
leaf01:
myasn: "64603"
routerid: "10.30.1.1"
internal_fabric:
interfaces: ["swp49","swp50","swp51","swp52"]
prefix_list_in: "allow_rack_subnets"
prefix_list_out: "allow_default"
prefix_lists:
leaf01:
allow_default:
- "100 deny any"
allow_rack_subnets:
- "100 deny any"
Template file:
{% set bgpvars = bgp[ansible_hostname] -%}
!
router bgp {{ bgpvars.myasn }}
bgp router-id {{bgpvars.routerid}}
bgp bestpath as-path multipath-relax
aggregate-address {{bgpvars.aggregate}} summary-only
{% for network in bgpvars.networks %}
network {{network}}
{% endfor %}
{### Build the spine/leaf fabric ###}
neighbor internal_fabric peer-group
neighbor internal_fabric remote-as external
neighbor internal_fabric capability extended-nexthop
neighbor internal_fabric prefix-list {{bgpvars.internal_fabric.prefix_list_out}} out
neighbor internal_fabric prefix-list {{bgpvars.internal_fabric.prefix_list_in}} in
{### Configure interfaces ###}
{% for interface in bgpvars.internal_fabric.interfaces %}
neighbor {{interface}} interface v6only peer-group internal_fabric
{% endfor %}
!
address-family ipv6 unicast
neighbor internal_fabric activate
exit-address-family
!
{### Configure prefix lists ###}
{% set lists = prefix_lists[ansible_hostname] -%}
{% for list_name in lists.keys() -%}
{% for seq in lists[list_name] %}
ip prefix-list {{list_name}} seq {{seq}}
{% endfor %}
{% endfor %}
!
end