mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Add counter filter (#3921)
* Add counter filter * move counter filter doc to existing chapter * Use existing typerror exception from Counter * Match counter filter example task name and output
This commit is contained in:
parent
a2f72be6c8
commit
9642a15d34
6 changed files with 159 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -118,6 +118,8 @@ files:
|
||||||
$doc_fragments/xenserver.py:
|
$doc_fragments/xenserver.py:
|
||||||
maintainers: bvitnik
|
maintainers: bvitnik
|
||||||
labels: xenserver
|
labels: xenserver
|
||||||
|
$filters/counter.py:
|
||||||
|
maintainers: keilr
|
||||||
$filters/dict.py:
|
$filters/dict.py:
|
||||||
maintainers: felixfontein
|
maintainers: felixfontein
|
||||||
$filters/dict_kv.py:
|
$filters/dict_kv.py:
|
||||||
|
|
4
changelogs/fragments/3921-add-counter-filter-plugin.yml
Normal file
4
changelogs/fragments/3921-add-counter-filter-plugin.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
add plugin.filter:
|
||||||
|
- name: counter
|
||||||
|
description: Counts hashable elements in a sequence
|
|
@ -297,6 +297,84 @@ This produces:
|
||||||
|
|
||||||
.. versionadded: 2.0.0
|
.. versionadded: 2.0.0
|
||||||
|
|
||||||
|
Counting elements in a sequence
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``community.general.counter`` filter plugin allows you to count (hashable) elements in a sequence. Elements are returned as dictionary keys and their counts are stored as dictionary values.
|
||||||
|
|
||||||
|
.. code-block:: yaml+jinja
|
||||||
|
|
||||||
|
- name: Count character occurrences in a string
|
||||||
|
debug:
|
||||||
|
msg: "{{ 'abccbaabca' | community.general.counter }}"
|
||||||
|
|
||||||
|
- name: Count items in a list
|
||||||
|
debug:
|
||||||
|
msg: "{{ ['car', 'car', 'bike', 'plane', 'bike'] | community.general.counter }}"
|
||||||
|
|
||||||
|
This produces:
|
||||||
|
|
||||||
|
.. code-block:: ansible-output
|
||||||
|
|
||||||
|
TASK [Count character occurrences in a string] ********************************************
|
||||||
|
ok: [localhost] => {
|
||||||
|
"msg": {
|
||||||
|
"a": 4,
|
||||||
|
"b": 3,
|
||||||
|
"c": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TASK [Count items in a list] **************************************************************
|
||||||
|
ok: [localhost] => {
|
||||||
|
"msg": {
|
||||||
|
"bike": 2,
|
||||||
|
"car": 2,
|
||||||
|
"plane": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This plugin is useful for selecting resources based on current allocation:
|
||||||
|
|
||||||
|
.. code-block:: yaml+jinja
|
||||||
|
|
||||||
|
- name: Get ID of SCSI controller(s) with less than 4 disks attached and choose the one with the least disks
|
||||||
|
debug:
|
||||||
|
msg: >-
|
||||||
|
{{
|
||||||
|
( disks | dict2items | map(attribute='value.adapter') | list
|
||||||
|
| community.general.counter | dict2items
|
||||||
|
| rejectattr('value', '>=', 4) | sort(attribute='value') | first
|
||||||
|
).key
|
||||||
|
}}
|
||||||
|
vars:
|
||||||
|
disks:
|
||||||
|
sda:
|
||||||
|
adapter: scsi_1
|
||||||
|
sdb:
|
||||||
|
adapter: scsi_1
|
||||||
|
sdc:
|
||||||
|
adapter: scsi_1
|
||||||
|
sdd:
|
||||||
|
adapter: scsi_1
|
||||||
|
sde:
|
||||||
|
adapter: scsi_2
|
||||||
|
sdf:
|
||||||
|
adapter: scsi_3
|
||||||
|
sdg:
|
||||||
|
adapter: scsi_3
|
||||||
|
|
||||||
|
This produces:
|
||||||
|
|
||||||
|
.. code-block:: ansible-output
|
||||||
|
|
||||||
|
TASK [Get ID of SCSI controller(s) with less than 4 disks attached and choose the one with the least disks]
|
||||||
|
ok: [localhost] => {
|
||||||
|
"msg": "scsi_2"
|
||||||
|
}
|
||||||
|
|
||||||
|
.. versionadded:: 4.3.0
|
||||||
|
|
||||||
Working with times
|
Working with times
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
36
plugins/filter/counter.py
Normal file
36
plugins/filter/counter.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021, Remy Keil <remy.keil@gmail.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible.module_utils.common._collections_compat import Sequence
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
|
||||||
|
def counter(sequence):
|
||||||
|
''' Count elements in a sequence. Returns dict with count result. '''
|
||||||
|
if not isinstance(sequence, Sequence):
|
||||||
|
raise AnsibleFilterError('Argument for community.general.counter must be a sequence (string or list). %s is %s' %
|
||||||
|
(sequence, type(sequence)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = dict(Counter(sequence))
|
||||||
|
except TypeError as e:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
"community.general.counter needs a sequence with hashable elements (int, float or str) - %s" % (e)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
''' Ansible counter jinja2 filters '''
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
filters = {
|
||||||
|
'counter': counter,
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters
|
2
tests/integration/targets/filter_counter/aliases
Normal file
2
tests/integration/targets/filter_counter/aliases
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
shippable/posix/group2
|
||||||
|
skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller
|
37
tests/integration/targets/filter_counter/tasks/main.yml
Normal file
37
tests/integration/targets/filter_counter/tasks/main.yml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
- name: test counter filter
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "('abca' | community.general.counter) == {'a': 2, 'b': 1, 'c': 1}"
|
||||||
|
- "(['apple', 'pear', 'pear'] | community.general.counter) == {'apple': 1, 'pear': 2}"
|
||||||
|
- "([1, 2, 2, 3] | community.general.counter) == {1: 1, 2: 2, 3: 1}"
|
||||||
|
- "([1.11, 1.11, 1.12] | community.general.counter) == {1.11: 2, 1.12: 1}"
|
||||||
|
|
||||||
|
- name: test fail argument not a sequence
|
||||||
|
debug:
|
||||||
|
msg: "{{ {'a': 'b'} | community.general.counter }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: res
|
||||||
|
|
||||||
|
- name: verify test fail argument not a sequence
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- res is failed
|
||||||
|
- res.msg is match('Argument for community.general.counter must be a sequence')
|
||||||
|
|
||||||
|
- name: test fail element not hashable
|
||||||
|
debug:
|
||||||
|
msg: "{{ [{'a': 'b'}] | community.general.counter }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: res
|
||||||
|
|
||||||
|
- name: verify test fail element not hashable
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- res is failed
|
||||||
|
- res.msg is match('community.general.counter needs a sequence with hashable elements')
|
Loading…
Reference in a new issue