mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-09 04:00:31 -07:00
Merge 9eb4540386
into 70b5e362f9
This commit is contained in:
commit
6ffbebe90c
14 changed files with 873 additions and 0 deletions
.github
plugins
tests/integration/targets
filter_to_prettytable
prettytable
6
.github/BOTMETA.yml
vendored
6
.github/BOTMETA.yml
vendored
|
@ -15,6 +15,8 @@ files:
|
|||
labels: action
|
||||
$actions/iptables_state.py:
|
||||
maintainers: quidame
|
||||
$actions/prettytable.py:
|
||||
maintainers: tgadiev
|
||||
$actions/shutdown.py:
|
||||
maintainers: nitzmahone samdoran aminvakil
|
||||
$becomes/:
|
||||
|
@ -208,6 +210,8 @@ files:
|
|||
maintainers: resmo
|
||||
$filters/to_months.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_prettytable.py:
|
||||
maintainers: tgadiev
|
||||
$filters/to_seconds.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_time_unit.yml:
|
||||
|
@ -1133,6 +1137,8 @@ files:
|
|||
keywords: doas dragonfly freebsd iocage jail netbsd openbsd opnsense pfsense
|
||||
labels: bsd portinstall
|
||||
maintainers: $team_bsd berenddeboer
|
||||
$modules/prettytable.py:
|
||||
maintainers: tgadiev
|
||||
$modules/pritunl_:
|
||||
maintainers: Lowess
|
||||
$modules/profitbricks:
|
||||
|
|
146
plugins/action/prettytable.py
Normal file
146
plugins/action/prettytable.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, quidame <quidame@poivron.org>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
__metaclass__ = type # pylint: disable=C0103
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleUndefinedVariable
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.plugins.action import ActionBase
|
||||
|
||||
try:
|
||||
import prettytable
|
||||
|
||||
HAS_PRETTYTABLE = True
|
||||
except ImportError:
|
||||
HAS_PRETTYTABLE = False
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
"""Print prettytable from list of dicts."""
|
||||
|
||||
TRANSFERS_FILES = False
|
||||
_VALID_ARGS = frozenset(
|
||||
("data", "column_order", "header_names", "column_alignments")
|
||||
)
|
||||
_VALID_ALIGNMENTS = {"left", "center", "right", "l", "c", "r"}
|
||||
|
||||
def run(
|
||||
self, tmp: Optional[str] = None, task_vars: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars) # pylint: disable=R1725
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
try:
|
||||
self._handle_options()
|
||||
|
||||
if not HAS_PRETTYTABLE:
|
||||
raise AnsibleError(
|
||||
'You need to install "prettytable" python module before running this module'
|
||||
)
|
||||
|
||||
data = self._task.args.get("data")
|
||||
if data is None: # Only check for None, allow empty list
|
||||
raise AnsibleError("The 'data' parameter is required")
|
||||
|
||||
if not isinstance(data, list):
|
||||
raise AnsibleError(
|
||||
"Expected a list of dictionaries, got a string"
|
||||
if isinstance(data, (str, bytes))
|
||||
else f"Expected a list of dictionaries, got {type(data).__name__}"
|
||||
)
|
||||
|
||||
if data and not all(isinstance(item, dict) for item in data):
|
||||
invalid_item = next(item for item in data if not isinstance(item, dict))
|
||||
raise AnsibleError(
|
||||
"All items in the list must be dictionaries, got a string"
|
||||
if isinstance(invalid_item, (str, bytes))
|
||||
else f"All items in the list must be dictionaries, got {type(invalid_item).__name__}"
|
||||
)
|
||||
|
||||
# Special handling for empty data
|
||||
if not data:
|
||||
result["pretty_table"] = "++\n++"
|
||||
else:
|
||||
table = self._create_table(data)
|
||||
result["pretty_table"] = to_text(table)
|
||||
|
||||
result["_ansible_verbose_always"] = True
|
||||
result["failed"] = False
|
||||
|
||||
except (AnsibleError, AnsibleUndefinedVariable) as e:
|
||||
result["failed"] = True
|
||||
result["msg"] = str(e)
|
||||
|
||||
return result
|
||||
|
||||
def _handle_options(self) -> None:
|
||||
"""Validate module arguments."""
|
||||
argument_spec = {
|
||||
"data": {"type": "list", "required": True},
|
||||
"column_order": {"type": "list", "elements": "str"},
|
||||
"header_names": {"type": "list", "elements": "str"},
|
||||
"column_alignments": {"type": "dict"},
|
||||
}
|
||||
|
||||
self._options_context = self.validate_argument_spec(argument_spec)
|
||||
|
||||
def _create_table(self, data: List[Dict[str, Any]]) -> prettytable.PrettyTable:
|
||||
"""Create and configure the prettytable instance.
|
||||
|
||||
Args:
|
||||
data: List of dictionaries to format into a table
|
||||
|
||||
Returns:
|
||||
Configured PrettyTable instance
|
||||
"""
|
||||
table = prettytable.PrettyTable()
|
||||
|
||||
# Determine field names from data or column_order
|
||||
field_names = self._task.args.get("column_order") or list(data[0].keys())
|
||||
|
||||
# Set headers
|
||||
header_names = self._task.args.get("header_names")
|
||||
table.field_names = header_names if header_names else field_names
|
||||
|
||||
# Configure alignments
|
||||
self._configure_alignments(table, field_names)
|
||||
|
||||
# Add rows - use add_row instead of add_rows for compatibility with older versions
|
||||
rows = [[item.get(col, "") for col in field_names] for item in data]
|
||||
for row in rows:
|
||||
table.add_row(row)
|
||||
|
||||
return table
|
||||
|
||||
def _configure_alignments(
|
||||
self, table: prettytable.PrettyTable, field_names: List[str]
|
||||
) -> None:
|
||||
"""Configure column alignments for the table.
|
||||
|
||||
Args:
|
||||
table: The PrettyTable instance to configure
|
||||
field_names: List of field names to align
|
||||
"""
|
||||
column_alignments = self._task.args.get("column_alignments", {})
|
||||
if not isinstance(column_alignments, dict):
|
||||
return
|
||||
|
||||
for col_name, alignment in column_alignments.items():
|
||||
if col_name in field_names:
|
||||
alignment = alignment.lower()
|
||||
if alignment in self._VALID_ALIGNMENTS:
|
||||
table.align[col_name] = alignment[0]
|
||||
else:
|
||||
self._display.warning(
|
||||
f"Ignored invalid alignment '{alignment}' for column '{col_name}'. "
|
||||
f"Valid alignments are {list(self._VALID_ALIGNMENTS)}"
|
||||
)
|
183
plugins/filter/to_prettytable.py
Normal file
183
plugins/filter/to_prettytable.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2025, Timur Gadiev <timur@example.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: to_prettytable
|
||||
short_description: Format a list of dictionaries as an ASCII table
|
||||
version_added: "8.0.0"
|
||||
author: Timur Gadiev (@tgadiev)
|
||||
description:
|
||||
- This filter takes a list of dictionaries and formats it as an ASCII table using the I(prettytable) Python library.
|
||||
requirements:
|
||||
- prettytable
|
||||
options:
|
||||
_input:
|
||||
description: A list of dictionaries to format.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
column_order:
|
||||
description: List of column names to specify the order of columns in the table.
|
||||
type: list
|
||||
elements: string
|
||||
header_names:
|
||||
description: List of custom header names to use instead of dictionary keys.
|
||||
type: list
|
||||
elements: string
|
||||
column_alignments:
|
||||
description: Dictionary of column alignments. Keys are column names, values are alignments.
|
||||
type: dictionary
|
||||
suboptions:
|
||||
alignment:
|
||||
description: Alignment for the column. Must be one of C(left), C(center), C(right), C(l), C(c), or C(r).
|
||||
type: string
|
||||
choices: [left, center, right, l, c, r]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Display a list of users as a table
|
||||
vars:
|
||||
users:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
- name: Bob
|
||||
age: 30
|
||||
role: user
|
||||
debug:
|
||||
msg: "{{ users | community.general.to_prettytable }}"
|
||||
|
||||
- name: Display a table with custom column ordering
|
||||
debug:
|
||||
msg: "{{ users | community.general.to_prettytable('role', 'name', 'age') }}"
|
||||
|
||||
- name: Display a table with custom headers
|
||||
debug:
|
||||
msg: "{{ users | community.general.to_prettytable(header_names=['User Name', 'User Age', 'User Role']) }}"
|
||||
|
||||
- name: Display a table with custom alignments
|
||||
debug:
|
||||
msg: "{{ users | community.general.to_prettytable(column_alignments={'name': 'center', 'age': 'right', 'role': 'left'}) }}"
|
||||
|
||||
- name: Combine multiple options
|
||||
debug:
|
||||
msg: "{{ users | community.general.to_prettytable(
|
||||
column_order=['role', 'name', 'age'],
|
||||
header_names=['Position', 'Full Name', 'Years'],
|
||||
column_alignments={'name': 'center', 'age': 'right', 'role': 'left'}) }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The formatted ASCII table.
|
||||
type: string
|
||||
'''
|
||||
|
||||
try:
|
||||
import prettytable
|
||||
HAS_PRETTYTABLE = True
|
||||
except ImportError:
|
||||
HAS_PRETTYTABLE = False
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
|
||||
def to_prettytable(data, *args, **kwargs):
|
||||
"""Convert a list of dictionaries to an ASCII table.
|
||||
|
||||
Args:
|
||||
data: List of dictionaries to format
|
||||
*args: Optional list of column names to specify column order
|
||||
**kwargs: Optional keyword arguments:
|
||||
- column_order: List of column names to specify the order
|
||||
- header_names: List of custom header names
|
||||
- column_alignments: Dict of column alignments (left, center, right)
|
||||
|
||||
Returns:
|
||||
String containing the ASCII table
|
||||
"""
|
||||
if not HAS_PRETTYTABLE:
|
||||
raise AnsibleFilterError(
|
||||
'You need to install "prettytable" Python module to use this filter'
|
||||
)
|
||||
|
||||
if not isinstance(data, list):
|
||||
raise AnsibleFilterError(
|
||||
"Expected a list of dictionaries, got a string"
|
||||
if isinstance(data, string_types)
|
||||
else f"Expected a list of dictionaries, got {type(data).__name__}"
|
||||
)
|
||||
|
||||
# Handle empty data
|
||||
if not data:
|
||||
return "++\n++"
|
||||
|
||||
# Check that all items are dictionaries
|
||||
if not all(isinstance(item, dict) for item in data):
|
||||
invalid_item = next(item for item in data if not isinstance(item, dict))
|
||||
raise AnsibleFilterError(
|
||||
"All items in the list must be dictionaries, got a string"
|
||||
if isinstance(invalid_item, string_types)
|
||||
else f"All items in the list must be dictionaries, got {type(invalid_item).__name__}"
|
||||
)
|
||||
|
||||
# Handle positional argument column order
|
||||
column_order = kwargs.get('column_order', None)
|
||||
if args and not column_order:
|
||||
column_order = list(args)
|
||||
|
||||
# Create the table and configure it
|
||||
table = prettytable.PrettyTable()
|
||||
|
||||
# Determine field names
|
||||
field_names = column_order or list(data[0].keys())
|
||||
|
||||
# Set headers
|
||||
header_names = kwargs.get('header_names', None)
|
||||
table.field_names = header_names if header_names else field_names
|
||||
|
||||
# Configure alignments
|
||||
_configure_alignments(table, field_names, kwargs.get('column_alignments', {}))
|
||||
|
||||
# Add rows - use add_row instead of add_rows for compatibility with older versions
|
||||
rows = [[item.get(col, "") for col in field_names] for item in data]
|
||||
for row in rows:
|
||||
table.add_row(row)
|
||||
|
||||
return to_text(table)
|
||||
|
||||
|
||||
def _configure_alignments(table, field_names, column_alignments):
|
||||
"""Configure column alignments for the table.
|
||||
|
||||
Args:
|
||||
table: The PrettyTable instance to configure
|
||||
field_names: List of field names to align
|
||||
column_alignments: Dict of column alignments
|
||||
"""
|
||||
valid_alignments = {"left", "center", "right", "l", "c", "r"}
|
||||
|
||||
if not isinstance(column_alignments, dict):
|
||||
return
|
||||
|
||||
for col_name, alignment in column_alignments.items():
|
||||
if col_name in field_names:
|
||||
alignment = alignment.lower()
|
||||
if alignment in valid_alignments:
|
||||
table.align[col_name] = alignment[0]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
"""Ansible core jinja2 filters."""
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'to_prettytable': to_prettytable
|
||||
}
|
145
plugins/modules/prettytable.py
Normal file
145
plugins/modules/prettytable.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: prettytable
|
||||
short_description: Format data into an ASCII table using prettytable
|
||||
version_added: '10.6.0'
|
||||
author:
|
||||
- Timur Gadiev (@tgadiev)
|
||||
extends_documentation_fragment:
|
||||
- action_common_attributes
|
||||
- action_common_attributes.conn
|
||||
- action_common_attributes.flow
|
||||
- community.general.attributes
|
||||
- community.general.attributes.flow
|
||||
description:
|
||||
- This module formats a list of dictionaries into an ASCII table using the B(prettytable) Python library.
|
||||
- Useful for creating human-readable output from structured data.
|
||||
- Allows customization of column order, headers, and alignments.
|
||||
- The table is created with a clean, readable format suitable for terminal output.
|
||||
requirements:
|
||||
- prettytable
|
||||
attributes:
|
||||
action:
|
||||
support: full
|
||||
async:
|
||||
support: none
|
||||
become:
|
||||
support: none
|
||||
bypass_host_loop:
|
||||
support: none
|
||||
check_mode:
|
||||
support: full
|
||||
connection:
|
||||
support: none
|
||||
delegation:
|
||||
support: none
|
||||
details: This is a pure action plugin that runs entirely on the controller. Delegation has no effect as no tasks are executed on remote hosts.
|
||||
diff_mode:
|
||||
support: none
|
||||
platform:
|
||||
support: full
|
||||
platforms: all
|
||||
options:
|
||||
data:
|
||||
description:
|
||||
- List of dictionaries to format into a table.
|
||||
- Each dictionary in the list represents a row in the table.
|
||||
- Dictionary keys become column headers unless overridden by I(header_names).
|
||||
- If the list is empty, an empty table will be created.
|
||||
- All items in the list must be dictionaries.
|
||||
type: list
|
||||
elements: dict
|
||||
required: true
|
||||
column_order:
|
||||
description:
|
||||
- List of dictionary keys specifying the order of columns in the output table.
|
||||
- If not specified, uses the keys from the first dictionary in the input list.
|
||||
- Only the columns specified in this list will be included in the table.
|
||||
- Keys must exist in the input dictionaries.
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
header_names:
|
||||
description:
|
||||
- List of custom header names for the columns.
|
||||
- Must match the length of columns being displayed.
|
||||
- If not specified, uses the dictionary keys or I(column_order) values as headers.
|
||||
- Use this to provide more readable or formatted column headers.
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
column_alignments:
|
||||
description:
|
||||
- Dictionary mapping column names to their alignment.
|
||||
- Keys should be column names (either from input data or I(column_order)).
|
||||
- Values must be one of 'left', 'center', 'right' (or 'l', 'c', 'r').
|
||||
- Invalid alignment values will be ignored with a warning.
|
||||
- Columns not specified default to left alignment.
|
||||
- Alignments for non-existent columns are ignored.
|
||||
type: dict
|
||||
required: false
|
||||
notes:
|
||||
- This is an action plugin, meaning the plugin executes on the controller, rather than on the target host.
|
||||
- The prettytable Python library must be installed on the controller.
|
||||
- Column alignments are case-insensitive.
|
||||
- Missing values in input dictionaries are displayed as empty strings.
|
||||
seealso:
|
||||
- module: ansible.builtin.debug
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Basic usage with a list of dictionaries
|
||||
- name: Create a table from user data
|
||||
community.general.prettytable:
|
||||
data:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
- name: Bob
|
||||
age: 30
|
||||
role: user
|
||||
|
||||
# Specify column order and custom headers
|
||||
- name: Create a formatted table with custom headers
|
||||
community.general.prettytable:
|
||||
data:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
- name: Bob
|
||||
age: 30
|
||||
role: user
|
||||
column_order:
|
||||
- name
|
||||
- role
|
||||
- age
|
||||
header_names:
|
||||
- "User Name"
|
||||
- "User Role"
|
||||
- "User Age"
|
||||
|
||||
# Set column alignments for better number and text formatting
|
||||
- name: Create table with specific alignments
|
||||
community.general.prettytable:
|
||||
data:
|
||||
- date: "2023-01-01"
|
||||
description: "Office supplies"
|
||||
amount: 123.45
|
||||
- date: "2023-01-02"
|
||||
description: "Software license"
|
||||
amount: 500.00
|
||||
column_alignments:
|
||||
amount: right # Numbers right-aligned
|
||||
description: left # Text left-aligned
|
||||
date: center # Dates centered
|
||||
'''
|
5
tests/integration/targets/filter_to_prettytable/aliases
Normal file
5
tests/integration/targets/filter_to_prettytable/aliases
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
azp/posix/1
|
130
tests/integration/targets/filter_to_prettytable/tasks/main.yml
Normal file
130
tests/integration/targets/filter_to_prettytable/tasks/main.yml
Normal file
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# Copyright (c) 2025, Timur Gadiev (@tgadiev)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Install required libs
|
||||
pip:
|
||||
name: prettytable
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Set test data
|
||||
set_fact:
|
||||
test_data:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
- name: Bob
|
||||
age: 30
|
||||
role: user
|
||||
data_for_align:
|
||||
- date: 2023-01-01
|
||||
description: Office supplies
|
||||
amount: 123.45
|
||||
|
||||
# Test basic functionality
|
||||
- name: Test basic table creation
|
||||
set_fact:
|
||||
basic_table: '{{ test_data | community.general.to_prettytable }}'
|
||||
|
||||
- name: Verify basic table output
|
||||
assert:
|
||||
that:
|
||||
- basic_table | trim is defined
|
||||
- basic_table | trim is not none
|
||||
|
||||
# Test column ordering
|
||||
- name: Test column ordering
|
||||
set_fact:
|
||||
ordered_table: "{{ test_data | community.general.to_prettytable(column_order=['role', 'name', 'age']) }}"
|
||||
|
||||
- name: Verify ordered table output
|
||||
assert:
|
||||
that:
|
||||
- ordered_table | trim is defined
|
||||
- ordered_table | trim is not none
|
||||
- ordered_table | trim != basic_table | trim
|
||||
|
||||
# Test custom headers
|
||||
- name: Test custom headers
|
||||
set_fact:
|
||||
headers_table: "{{ test_data | community.general.to_prettytable(header_names=['User Name', 'User Age', 'User Role']) }}"
|
||||
|
||||
- name: Verify custom headers output
|
||||
assert:
|
||||
that:
|
||||
- headers_table | trim is defined
|
||||
- headers_table | trim is not none
|
||||
- "headers_table | trim is search('User Name')"
|
||||
- "headers_table | trim is search('User Age')"
|
||||
- "headers_table | trim is search('User Role')"
|
||||
|
||||
# Test alignments
|
||||
- name: Test column alignments
|
||||
set_fact:
|
||||
aligned_table: "{{ data_for_align | community.general.to_prettytable(column_alignments={'amount': 'right', 'description': 'left', 'date': 'center'}) }}"
|
||||
|
||||
- name: Verify aligned table output
|
||||
assert:
|
||||
that:
|
||||
- aligned_table | trim is defined
|
||||
- aligned_table | trim is not none
|
||||
|
||||
# Test combined options
|
||||
- name: Test combined options
|
||||
set_fact:
|
||||
combined_table: "{{ test_data | community.general.to_prettytable(
|
||||
column_order=['role', 'name', 'age'],
|
||||
header_names=['Position', 'Full Name', 'Years'],
|
||||
column_alignments={'name': 'center', 'age': 'right', 'role': 'left'}) }}"
|
||||
|
||||
- name: Verify combined table output
|
||||
assert:
|
||||
that:
|
||||
- combined_table | trim is defined
|
||||
- combined_table | trim is not none
|
||||
- "combined_table | trim is search('Position')"
|
||||
- "combined_table | trim is search('Full Name')"
|
||||
- "combined_table | trim is search('Years')"
|
||||
|
||||
# Test empty data
|
||||
- name: Test empty data list
|
||||
set_fact:
|
||||
empty_table: "{{ [] | community.general.to_prettytable }}"
|
||||
|
||||
- name: Verify empty table output
|
||||
assert:
|
||||
that:
|
||||
- empty_table | trim == "++\n++" | trim
|
||||
|
||||
# Test error conditions
|
||||
- name: Test non-list input (expect failure)
|
||||
block:
|
||||
- set_fact:
|
||||
invalid_table: "{{ 'not_a_list' | community.general.to_prettytable }}"
|
||||
register: failure_result
|
||||
ignore_errors: true
|
||||
- name: Verify error message for non-list input
|
||||
assert:
|
||||
that:
|
||||
- failure_result is failed
|
||||
- "'Expected a list of dictionaries, got a string' in failure_result.msg"
|
||||
|
||||
- name: Test list with non-dictionary items (expect failure)
|
||||
block:
|
||||
- set_fact:
|
||||
invalid_table: "{{ ['not_a_dict', 'also_not_a_dict'] | community.general.to_prettytable }}"
|
||||
register: failure_result
|
||||
ignore_errors: true
|
||||
- name: Verify error message for non-dictionary items
|
||||
assert:
|
||||
that:
|
||||
- failure_result is failed
|
||||
- "'All items in the list must be dictionaries' in failure_result.msg"
|
5
tests/integration/targets/prettytable/aliases
Normal file
5
tests/integration/targets/prettytable/aliases
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
azp/posix/1
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
+------------+-----------------+--------+
|
||||
| date | description | amount |
|
||||
+------------+-----------------+--------+
|
||||
| 2023-01-01 | Office supplies | 123.45 |
|
||||
+------------+-----------------+--------+
|
10
tests/integration/targets/prettytable/files/basic_table.txt
Normal file
10
tests/integration/targets/prettytable/files/basic_table.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
+-------+-----+-------+
|
||||
| name | age | role |
|
||||
+-------+-----+-------+
|
||||
| Alice | 25 | admin |
|
||||
| Bob | 30 | user |
|
||||
+-------+-----+-------+
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
+-----------+----------+-----------+
|
||||
| User Name | User Age | User Role |
|
||||
+-----------+----------+-----------+
|
||||
| Alice | 25 | admin |
|
||||
+-----------+----------+-----------+
|
|
@ -0,0 +1,6 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
++
|
||||
++
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
+-------+-------+-----+
|
||||
| role | name | age |
|
||||
+-------+-------+-----+
|
||||
| admin | Alice | 25 |
|
||||
| user | Bob | 30 |
|
||||
+-------+-------+-----+
|
4
tests/integration/targets/prettytable/meta/main.yml
Normal file
4
tests/integration/targets/prettytable/meta/main.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
# Copyright (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
205
tests/integration/targets/prettytable/tasks/main.yml
Normal file
205
tests/integration/targets/prettytable/tasks/main.yml
Normal file
|
@ -0,0 +1,205 @@
|
|||
---
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# Copyright (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Install required libs
|
||||
pip:
|
||||
name: prettytable
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
# Test basic functionality
|
||||
- name: Test basic table creation
|
||||
prettytable:
|
||||
data:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
- name: Bob
|
||||
age: 30
|
||||
role: user
|
||||
register: basic_result
|
||||
|
||||
- name: Load expected basic table output
|
||||
set_fact:
|
||||
expected_basic_table_raw: "{{ lookup('file', 'basic_table.txt') }}"
|
||||
|
||||
- name: Remove copyright header from expected output
|
||||
set_fact:
|
||||
expected_basic_table: "{{ expected_basic_table_raw.split('\n')[4:] | join('\n') }}"
|
||||
|
||||
- name: Verify basic table output
|
||||
assert:
|
||||
that:
|
||||
- basic_result.pretty_table | trim == expected_basic_table | trim
|
||||
- not basic_result.failed
|
||||
|
||||
# Test column ordering
|
||||
- name: Test table with custom column order
|
||||
prettytable:
|
||||
data:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
- name: Bob
|
||||
age: 30
|
||||
role: user
|
||||
column_order:
|
||||
- role
|
||||
- name
|
||||
- age
|
||||
register: ordered_result
|
||||
|
||||
- name: Load expected ordered table output
|
||||
set_fact:
|
||||
expected_ordered_table_raw: "{{ lookup('file', 'ordered_table.txt') }}"
|
||||
|
||||
- name: Remove copyright header from expected output
|
||||
set_fact:
|
||||
expected_ordered_table: "{{ expected_ordered_table_raw.split('\n')[4:] | join('\n') }}"
|
||||
|
||||
- name: Verify ordered table output
|
||||
assert:
|
||||
that:
|
||||
- ordered_result.pretty_table | trim == expected_ordered_table | trim
|
||||
- not ordered_result.failed
|
||||
|
||||
# Test custom headers
|
||||
- name: Test table with custom headers
|
||||
prettytable:
|
||||
data:
|
||||
- name: Alice
|
||||
age: 25
|
||||
role: admin
|
||||
header_names:
|
||||
- "User Name"
|
||||
- "User Age"
|
||||
- "User Role"
|
||||
register: headers_result
|
||||
|
||||
- name: Load expected headers table output
|
||||
set_fact:
|
||||
expected_headers_table_raw: "{{ lookup('file', 'custom_headers.txt') }}"
|
||||
|
||||
- name: Remove copyright header from expected output
|
||||
set_fact:
|
||||
expected_headers_table: "{{ expected_headers_table_raw.split('\n')[4:] | join('\n') }}"
|
||||
|
||||
- name: Verify custom headers output
|
||||
assert:
|
||||
that:
|
||||
- headers_result.pretty_table | trim == expected_headers_table | trim
|
||||
- not headers_result.failed
|
||||
|
||||
# Test column alignments
|
||||
- name: Test table with alignments
|
||||
prettytable:
|
||||
data:
|
||||
- date: "2023-01-01"
|
||||
description: "Office supplies"
|
||||
amount: 123.45
|
||||
column_alignments:
|
||||
amount: right
|
||||
description: left
|
||||
date: center
|
||||
register: aligned_result
|
||||
|
||||
- name: Load expected aligned table output
|
||||
set_fact:
|
||||
expected_aligned_table_raw: "{{ lookup('file', 'aligned_table.txt') }}"
|
||||
|
||||
- name: Remove copyright header from expected output
|
||||
set_fact:
|
||||
expected_aligned_table: "{{ expected_aligned_table_raw.split('\n')[4:] | join('\n') }}"
|
||||
|
||||
- name: Verify aligned table output
|
||||
assert:
|
||||
that:
|
||||
- aligned_result.pretty_table | trim == expected_aligned_table | trim
|
||||
- not aligned_result.failed
|
||||
|
||||
# Test error conditions
|
||||
- name: Test missing data parameter (should fail)
|
||||
prettytable: {} # Empty dict to test missing required parameter
|
||||
register: missing_data_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Verify error for missing data
|
||||
vars:
|
||||
expected_msg: 'missing required arguments: data'
|
||||
assert:
|
||||
that:
|
||||
- missing_data_result.failed
|
||||
- expected_msg in missing_data_result.msg
|
||||
|
||||
- name: Test invalid data type (should fail)
|
||||
prettytable:
|
||||
data: "this is a string"
|
||||
register: invalid_data_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Verify error for invalid data
|
||||
vars:
|
||||
expected_msg: "Expected a list of dictionaries, got a string"
|
||||
assert:
|
||||
that:
|
||||
- invalid_data_result.failed
|
||||
- expected_msg == invalid_data_result.msg
|
||||
|
||||
- name: Test list with invalid items (should fail)
|
||||
prettytable:
|
||||
data:
|
||||
- {"valid": "dict"}
|
||||
- "invalid string item"
|
||||
register: invalid_items_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Verify error for invalid items
|
||||
vars:
|
||||
expected_msg: "All items in the list must be dictionaries, got a string"
|
||||
assert:
|
||||
that:
|
||||
- invalid_items_result.failed
|
||||
- expected_msg == invalid_items_result.msg
|
||||
|
||||
# Test empty data
|
||||
- name: Test empty data list
|
||||
prettytable:
|
||||
data: []
|
||||
register: empty_result
|
||||
|
||||
- name: Load expected empty table output
|
||||
set_fact:
|
||||
expected_empty_table_raw: "{{ lookup('file', 'empty_table.txt') }}"
|
||||
|
||||
- name: Remove copyright header from expected output
|
||||
set_fact:
|
||||
expected_empty_table: "{{ expected_empty_table_raw.split('\n')[4:] | join('\n') }}"
|
||||
|
||||
- name: Verify empty table output
|
||||
assert:
|
||||
that:
|
||||
- empty_result.pretty_table | trim == expected_empty_table | trim
|
||||
- not empty_result.failed
|
||||
|
||||
# Test invalid alignments
|
||||
- name: Test invalid alignment value
|
||||
prettytable:
|
||||
data:
|
||||
- col1: value1
|
||||
col2: value2
|
||||
column_alignments:
|
||||
col1: invalid
|
||||
register: invalid_alignment_result
|
||||
|
||||
- name: Verify invalid alignment handling
|
||||
assert:
|
||||
that:
|
||||
- not invalid_alignment_result.failed # Should not fail, just warn
|
Loading…
Add table
Reference in a new issue