1
0
Fork 0
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:
Rémy Keil 2021-12-26 14:56:21 +01:00 committed by GitHub
parent a2f72be6c8
commit 9642a15d34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 159 additions and 0 deletions

2
.github/BOTMETA.yml vendored
View file

@ -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:

View file

@ -0,0 +1,4 @@
---
add plugin.filter:
- name: counter
description: Counts hashable elements in a sequence

View file

@ -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
View 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

View 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

View 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')