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:
|
||||
maintainers: bvitnik
|
||||
labels: xenserver
|
||||
$filters/counter.py:
|
||||
maintainers: keilr
|
||||
$filters/dict.py:
|
||||
maintainers: felixfontein
|
||||
$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
|
||||
|
||||
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
|
||||
------------------
|
||||
|
||||
|
|
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