diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index f83194be4f4..aab48a009fd 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -544,43 +544,6 @@ def list_of_dict_key_value_elements_to_dict(mylist, key_name='key', value_name=' return dict((item[key_name], item[value_name]) for item in mylist) -def random_mac(value, seed=None): - ''' 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)) - - if seed is None: - r = SystemRandom() - else: - r = Random(seed) - # Generate random int between x1000000000 and xFFFFFFFFFF - v = r.randint(68719476736, 1099511627775) - # 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) - - def path_join(paths): ''' takes a sequence or a string, and return a concatenation of the different members ''' @@ -684,7 +647,4 @@ class FilterModule(object): 'dict2items': dict_to_list_of_dict_key_value_elements, 'items2dict': list_of_dict_key_value_elements_to_dict, 'subelements': subelements, - - # Misc - 'random_mac': random_mac, } diff --git a/lib/ansible/plugins/filter/random_mac.py b/lib/ansible/plugins/filter/random_mac.py new file mode 100644 index 00000000000..aa9f59be081 --- /dev/null +++ b/lib/ansible/plugins/filter/random_mac.py @@ -0,0 +1,73 @@ +# (c) 2020 Ansible Project +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +from random import Random, SystemRandom + +from ansible.errors import AnsibleFilterError +from ansible.module_utils.six import string_types + + +def random_mac(value, seed=None): + ''' 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 not mac: + err += ",empty item" + continue + if not re.match('[a-f0-9]{2}', mac): + err += ",%s not hexa byte" % mac + err = err.strip(',') + + if err: + raise AnsibleFilterError('Invalid value (%s) for random_mac: %s' % (value, err)) + + if seed is None: + r = SystemRandom() + else: + r = Random(seed) + # Generate random int between x1000000000 and xFFFFFFFFFF + v = r.randint(68719476736, 1099511627775) + # 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: + ''' Ansible jinja2 filters ''' + def filters(self): + return { + 'random_mac': random_mac, + } diff --git a/test/integration/targets/filter_core/tasks/main.yml b/test/integration/targets/filter_core/tasks/main.yml index e14889eb23e..2a1f8c76a4a 100644 --- a/test/integration/targets/filter_core/tasks/main.yml +++ b/test/integration/targets/filter_core/tasks/main.yml @@ -136,65 +136,6 @@ - "'Ansible - くらとみ\n' | b64encode(encoding='utf-16-le') == 'QQBuAHMAaQBiAGwAZQAgAC0AIABPMIkwaDB/MAoA'" - "'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' | random_mac is match('^00:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]$')" - - "'00:00' | random_mac is match('^00:00:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]$')" - - "'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: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:00:00' | random_mac is match('^00:00:00:00:00:[a-f0-9][a-f0-9]$')" - - "'00:00:00' | random_mac != '00:00:00' | random_mac" - -- name: Verify random_mac filter with seed - assert: - that: - - "'00:00:00' | random_mac(seed='test') == '00:00:00' | random_mac(seed='test')" - - "'00:00:00' | random_mac(seed='test') != '00:00:00' | random_mac(seed='another_test')" - -- name: Ensure dict2items works with hostvars - debug: - msg: "{{ item.key }}" - loop: "{{ hostvars|dict2items }}" - loop_control: - label: "{{ item.key }}" - - name: Ensure combining two dictionaries containing undefined variables provides a helpful error block: - set_fact: diff --git a/test/integration/targets/filter_random_mac/aliases b/test/integration/targets/filter_random_mac/aliases new file mode 100644 index 00000000000..1603f4351b6 --- /dev/null +++ b/test/integration/targets/filter_random_mac/aliases @@ -0,0 +1,3 @@ +shippable/posix/group2 +skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller +skip/aix diff --git a/test/integration/targets/filter_random_mac/tasks/main.yml b/test/integration/targets/filter_random_mac/tasks/main.yml new file mode 100644 index 00000000000..3c90127262b --- /dev/null +++ b/test/integration/targets/filter_random_mac/tasks/main.yml @@ -0,0 +1,59 @@ +# test code for filters +# Copyright: (c) 2014, Michael DeHaan +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- set_fact: + output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}" + +- 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' | random_mac is match('^00:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]$')" + - "'00:00' | random_mac is match('^00:00:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]:[a-f0-9][a-f0-9]$')" + - "'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: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:00:00' | random_mac is match('^00:00:00:00:00:[a-f0-9][a-f0-9]$')" + - "'00:00:00' | random_mac != '00:00:00' | random_mac" + +- name: Verify random_mac filter with seed + assert: + that: + - "'00:00:00' | random_mac(seed='test') == '00:00:00' | random_mac(seed='test')" + - "'00:00:00' | random_mac(seed='test') != '00:00:00' | random_mac(seed='another_test')"