diff --git a/changelogs/fragments/add_random_mac_filtter.yaml b/changelogs/fragments/add_random_mac_filtter.yaml new file mode 100644 index 00000000000..0759d8a6277 --- /dev/null +++ b/changelogs/fragments/add_random_mac_filtter.yaml @@ -0,0 +1,6 @@ +--- +minor_changes: + - Added new filter to generate random MAC addresses from a given + string acting as a prefix. Refer to the appropriate entry + which has been added to user_guide playbook_filters.rst + document. diff --git a/docs/docsite/rst/user_guide/playbooks_filters.rst b/docs/docsite/rst/user_guide/playbooks_filters.rst index d028dbb28dd..8c0a7404359 100644 --- a/docs/docsite/rst/user_guide/playbooks_filters.rst +++ b/docs/docsite/rst/user_guide/playbooks_filters.rst @@ -243,6 +243,22 @@ An example of using this filter with ``loop``:: key: "{{ lookup('file', item.1) }}" loop: "{{ users|subelements('authorized') }}" +.. _random_mac_filter: + +Random Mac Address Filter +````````````````````````` + +.. versionadded:: 2.6 + +This filter can be used to generate a random MAC address from a string prefix. + +To get a random MAC address from a string prefix starting with '52:54:00':: + + "{{ '52:54:00'|random_mac }}" + # => '52:54:00:ef:1c:03' + +Note that if anything is wrong with the prefix string, the filter will issue an error. + .. _random_filter: Random Number Filter diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index f6f6e28cde4..5288b3bd8dc 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -37,7 +37,7 @@ import yaml from collections import MutableMapping, MutableSequence import datetime from functools import partial -from random import Random, SystemRandom, shuffle +from random import Random, SystemRandom, shuffle, random from jinja2.filters import environmentfilter, do_groupby as _do_groupby @@ -532,6 +532,39 @@ def dict_to_list_of_dict_key_value_elements(mydict): return ret +def random_mac(value): + ''' takes string prefix, and return it completed with random bytes + to get a complete 6 bytes MAC address ''' + + if not isinstance(value, string_types): + raise AnsibleFilterError('Invalid value type (%s) for random_mac (%s)' % (type(value), value)) + + value = value.lower() + mac_items = value.split(':') + + if len(mac_items) > 5: + raise AnsibleFilterError('Invalid value (%s) for random_mac: 5 colon(:) separated items max' % value) + + err = "" + for mac in mac_items: + if len(mac) == 0: + err += ",empty item" + continue + if not re.match('[a-f0-9]{2}', mac): + err += ",%s not hexa byte" % mac + err = err.strip(',') + + if len(err): + raise AnsibleFilterError('Invalid value (%s) for random_mac: %s' % (value, err)) + + # Generate random float and make it int + v = int(random() * 10.0**10) + # Select first n chars to complement input prefix + remain = 2 * (6 - len(mac_items)) + rnd = ('%x' % v)[:remain] + return value + re.sub(r'(..)', r':\1', rnd) + + class FilterModule(object): ''' Ansible core jinja2 filters ''' @@ -621,4 +654,7 @@ class FilterModule(object): 'flatten': flatten, 'dict2items': dict_to_list_of_dict_key_value_elements, 'subelements': subelements, + + # Misc + 'random_mac': random_mac, } diff --git a/test/integration/targets/filters/tasks/main.yml b/test/integration/targets/filters/tasks/main.yml index a2394dc1fa0..baaa4fa784e 100644 --- a/test/integration/targets/filters/tasks/main.yml +++ b/test/integration/targets/filters/tasks/main.yml @@ -192,4 +192,46 @@ - "'Ansible - くらとみ\n' | b64encode == 'QW5zaWJsZSAtIOOBj+OCieOBqOOBvwo='" - "'QW5zaWJsZSAtIOOBj+OCieOBqOOBvwo=' | b64decode == 'Ansible - くらとみ\n'" - "'Ansible - くらとみ\n' | b64encode(encoding='utf-16-le') == 'QQBuAHMAaQBiAGwAZQAgAC0AIABPMIkwaDB/MAoA'" - - "'QQBuAHMAaQBiAGwAZQAgAC0AIABPMIkwaDB/MAoA' | b64decode(encoding='utf-16-le') == 'Ansible - くらとみ\n'" \ No newline at end of file + - "'QQBuAHMAaQBiAGwAZQAgAC0AIABPMIkwaDB/MAoA' | b64decode(encoding='utf-16-le') == 'Ansible - くらとみ\n'" + +- name: Test random_mac filter bad argument type + debug: + var: "0 | random_mac" + register: _bad_random_mac_filter + ignore_errors: yes + +- name: Verify random_mac filter showed a bad argument type error message + assert: + that: + - _bad_random_mac_filter is failed + - "_bad_random_mac_filter.msg is match('Invalid value type (.*int.*) for random_mac .*')" + +- name: Test random_mac filter bad argument value + debug: + var: "'dummy' | random_mac" + register: _bad_random_mac_filter + ignore_errors: yes + +- name: Verify random_mac filter showed a bad argument value error message + assert: + that: + - _bad_random_mac_filter is failed + - "_bad_random_mac_filter.msg is match('Invalid value (.*) for random_mac: .* not hexa byte')" + +- name: Test random_mac filter prefix too big + debug: + var: "'00:00:00:00:00:00' | random_mac" + register: _bad_random_mac_filter + ignore_errors: yes + +- name: Verify random_mac filter showed a prefix too big error message + assert: + that: + - _bad_random_mac_filter is failed + - "_bad_random_mac_filter.msg is match('Invalid value (.*) for random_mac: 5 colon.* separated items max')" + +- name: Verify random_mac filter + assert: + that: + - "'00:00:00' | random_mac is match('^00:00:00:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]$')" + - "'00:00:00' | random_mac != '00:00:00' | random_mac"