diff --git a/filter_plugins/netbox.py b/filter_plugins/netbox.py index 323fcda..23a4f86 100644 --- a/filter_plugins/netbox.py +++ b/filter_plugins/netbox.py @@ -7,11 +7,26 @@ class FilterModule(object): def filters(self): return { + 'compact_numlist': self.compact_numlist, 'iface_real': self.iface_real, 'iface_peer': self.iface_peer, 'iface_vlans': self.iface_vlans } + def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-'): + '''Transform [1,2,3,5,7,8,9] into "1-3,5,7-9"''' + if sort: + nums = sorted(nums) + i = 0 + spans = [] + while i < len(nums): + j = i + 1 + while j < len(nums) and nums[j]-nums[i] == j-i: + j += 1 + spans += [f'{nums[i]}{range_delimiter}{nums[j-1]}' if j > i+1 else f'{nums[i]}'] + i = j + return delimiter.join(spans) + def iface_real(self, interfaces): '''Return only non-virtual interfaces''' for iface in interfaces: diff --git a/roles/access/tasks/main.yml b/roles/access/tasks/main.yml index e259f31..8cd7fd2 100644 --- a/roles/access/tasks/main.yml +++ b/roles/access/tasks/main.yml @@ -2,19 +2,9 @@ set_fact: ansible_ssh_pass: '{{ lookup("passwordstore", "switch/"~inventory_hostname, subkey="pass") }}' -# This should be provided by the netbox inventory plugin but isn’t yet. -# https://github.com/netbox-community/ansible_modules/issues/1007 -- name: Fetch configuration from netbox - uri: - url: '{{ interfaces[0].device.url }}render-config/' # why URL can only be accessed through interface data is a mystery - method: POST - headers: - Authorization: 'Token {{ lookup("env", "NETBOX_API_TOKEN") }}' - register: config - - name: Set configuration ansible.netcommon.cli_config: - config: '{{ config.json.content }}' + config: '{{ lookup("template", "config-"~manufacturer~"-"~device_type~".j2") }}' register: result # These lines are not displayed by 'sho ru' and always reported as different, so ignore them. changed_when: result.commands | reject('match', '^(no shutdown|no switchport access vlan|switchport mode access|switchport mode hybrid|interface .*|no enable service web-server https?)$') diff --git a/roles/access/templates/config-d-link-dgs-1510-52x.j2 b/roles/access/templates/config-d-link-dgs-1510-52x.j2 new file mode 120000 index 0000000..a673dd4 --- /dev/null +++ b/roles/access/templates/config-d-link-dgs-1510-52x.j2 @@ -0,0 +1 @@ +config-d-link.j2 \ No newline at end of file diff --git a/roles/access/templates/config-d-link-dgs-1510-52xmp.j2 b/roles/access/templates/config-d-link-dgs-1510-52xmp.j2 new file mode 120000 index 0000000..a673dd4 --- /dev/null +++ b/roles/access/templates/config-d-link-dgs-1510-52xmp.j2 @@ -0,0 +1 @@ +config-d-link.j2 \ No newline at end of file diff --git a/roles/access/templates/config-d-link.j2 b/roles/access/templates/config-d-link.j2 new file mode 100644 index 0000000..4e09bc0 --- /dev/null +++ b/roles/access/templates/config-d-link.j2 @@ -0,0 +1,68 @@ +terminal length default 0 +line console +line telnet +line ssh + +port-channel load-balance src-dst-ip + +ip ssh server + +vlan {{ vlans | map(attribute='vid') | compact_numlist }} + +{% for iface in interfaces | selectattr('lag') %} +interface {{ iface.name }} +{% if iface.enabled %} no{% endif %} shutdown + channel-group {{ iface.lag.name | select('in', '0123456789') | join('') }} mode active + +{% endfor %} + +{%- set mgmt = namespace(ip=false, gw=false) %} +{%- for iface in interfaces | rejectattr('lag') %} +interface {{ iface.name }} +{% if iface.type.value != 'lag' %} +{% if iface.enabled %} no shutdown{% else %} shutdown{% endif %} +{% endif %} + +{%+ if iface.mode and iface.mode.value == 'access' %} + switchport mode access +{% if iface.untagged_vlan and iface.untagged_vlan.vid != 1 %} + switchport access vlan {{ iface.untagged_vlan.vid }} +{% else %} + no switchport access vlan +{% endif %} + +{%+ elif iface.mode and iface.mode.value == 'tagged' %} + switchport mode trunk + switchport trunk allowed vlan {{ (iface.tagged_vlans or vlans) | map(attribute='vid') | compact_numlist }} + +{%+ else %} + +{% endif %} + +{%- if iface.mgmt_only and iface.ip_addresses %} +{% set mgmt.ip = iface.ip_addresses[0].address %} +{% if iface.custom_fields.gateway %} +{% set mgmt.gw = iface.custom_fields.gateway.address %} +{% endif %} +{% endif %} +{% endfor %} + +{%- if mgmt.ip %} +interface Vlan1 + ip address {{ mgmt.ip | ipaddr('address') }} {{ mgmt.ip | ipaddr('netmask') }} +{% endif %} + +snmp-server name {{ inventory_hostname }} + +sntp enable +{% for address in ntp %} +sntp server {{ address }} +{% endfor %} + +ntp access-group default nomodify noquery + +{% if mgmt.gw %} +ip route 0.0.0.0 0.0.0.0 {{ mgmt.gw | ipaddr('address') }} primary +{% endif %} + +no ddp \ No newline at end of file diff --git a/roles/access/templates/config-fs-s5860-48xmg-u.j2 b/roles/access/templates/config-fs-s5860-48xmg-u.j2 new file mode 120000 index 0000000..ffb3016 --- /dev/null +++ b/roles/access/templates/config-fs-s5860-48xmg-u.j2 @@ -0,0 +1 @@ +config-fs.j2 \ No newline at end of file diff --git a/roles/access/templates/config-fs.j2 b/roles/access/templates/config-fs.j2 new file mode 100644 index 0000000..d21bfe2 --- /dev/null +++ b/roles/access/templates/config-fs.j2 @@ -0,0 +1,41 @@ +hostname {{ inventory_hostname }} + +no netconf enable + +no enable service telnet-server +no enable service web-server http +no enable service web-server https + +vlan range {{ vlans | map(attribute='vid') | union([1]) | compact_numlist }} + +{% for iface in interfaces %} +interface {{ iface.name }} +{% if iface.enabled %} no{% endif %} shutdown +{% if iface.lag %} + port-group {{ iface.lag.name | select('in', '0123456789') | join('') }} mode active + +{% elif iface.mgmt_only %} +{% if iface.ip_addresses %} +{% set address = iface.ip_addresses | map(attribute='address') | first %} + ip address {{ address | ipaddr('address') }} {{ address | ipaddr('netmask') }} +{% if iface.custom_fields.gateway %} + gateway {{ iface.custom_fields.gateway.address | ipaddr('address') }} +{%- endif %} +{%- endif %} + +{% else %} + mtu {{ iface.mtu | default('9216', true) }} +{% if iface.mode and iface.mode.value == 'access' %} + switchport mode access +{% if iface.untagged_vlan and iface.untagged_vlan.vid != 1 %} + switchport access vlan {{ iface.untagged_vlan.vid }} +{% else %} + no switchport access vlan +{% endif %} +{%- elif iface.mode and iface.mode.value == 'tagged' %} + switchport mode trunk + switchport trunk allowed vlan only {{ (iface.tagged_vlans or vlans) | map(attribute='vid') | compact_numlist }} +{% endif %} + +{% endif %} +{% endfor %}