Initial project commit
This commit is contained in:
@ -0,0 +1,72 @@
|
||||
# Copyright (c) 2021, Felix Fontein <felix@fontein.de>
|
||||
# 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: a_module
|
||||
short_description: Test whether a given string refers to an existing module or action plugin
|
||||
version_added: 4.0.0
|
||||
author: Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Test whether a given string refers to an existing module or action plugin.
|
||||
- This can be useful in roles, which can use this to ensure that required modules are present ahead of time.
|
||||
options:
|
||||
_input:
|
||||
description: A string denoting a fully qualified collection name (FQCN) of a module or action plugin.
|
||||
type: string
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Make sure that community.aws.route53 is available
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >
|
||||
'community.aws.route53' is community.general.a_module
|
||||
|
||||
- name: Make sure that community.general.does_not_exist is not a module or action plugin
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'community.general.does_not_exist' is not community.general.a_module"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: Whether the module or action plugin denoted by the input exists.
|
||||
type: boolean
|
||||
'''
|
||||
|
||||
from ansible.plugins.loader import action_loader, module_loader
|
||||
|
||||
try:
|
||||
from ansible.errors import AnsiblePluginRemovedError
|
||||
except ImportError:
|
||||
AnsiblePluginRemovedError = Exception
|
||||
|
||||
|
||||
def a_module(term):
|
||||
"""
|
||||
Example:
|
||||
- 'community.general.ufw' is community.general.a_module
|
||||
- 'community.general.does_not_exist' is not community.general.a_module
|
||||
"""
|
||||
try:
|
||||
for loader in (action_loader, module_loader):
|
||||
data = loader.find_plugin(term)
|
||||
if data is not None:
|
||||
return True
|
||||
return False
|
||||
except AnsiblePluginRemovedError:
|
||||
return False
|
||||
|
||||
|
||||
class TestModule(object):
|
||||
''' Ansible jinja2 tests '''
|
||||
|
||||
def tests(self):
|
||||
return {
|
||||
'a_module': a_module,
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@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
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: ansible_type
|
||||
short_description: Validate input type
|
||||
version_added: "9.2.0"
|
||||
author: Vladimir Botka (@vbotka)
|
||||
description: This test validates input type.
|
||||
options:
|
||||
_input:
|
||||
description: Input data.
|
||||
type: raw
|
||||
required: true
|
||||
dtype:
|
||||
description: A single data type, or a data types list to be validated.
|
||||
type: raw
|
||||
required: true
|
||||
alias:
|
||||
description: Data type aliases.
|
||||
default: {}
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
# Substitution converts str to AnsibleUnicode
|
||||
# -------------------------------------------
|
||||
|
||||
# String. AnsibleUnicode.
|
||||
dtype: AnsibleUnicode
|
||||
data: "abc"
|
||||
result: '{{ data is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# String. AnsibleUnicode alias str.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
dtype: str
|
||||
data: "abc"
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
|
||||
# List. All items are AnsibleUnicode.
|
||||
dtype: list[AnsibleUnicode]
|
||||
data: ["a", "b", "c"]
|
||||
result: '{{ data is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Dictionary. All keys are AnsibleUnicode. All values are AnsibleUnicode.
|
||||
dtype: dict[AnsibleUnicode, AnsibleUnicode]
|
||||
data: {"a": "foo", "b": "bar", "c": "baz"}
|
||||
result: '{{ data is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# No substitution and no alias. Type of strings is str
|
||||
# ----------------------------------------------------
|
||||
|
||||
# String
|
||||
dtype: str
|
||||
result: '{{ "abc" is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Integer
|
||||
dtype: int
|
||||
result: '{{ 123 is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Float
|
||||
dtype: float
|
||||
result: '{{ 123.45 is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Boolean
|
||||
dtype: bool
|
||||
result: '{{ true is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# List. All items are strings.
|
||||
dtype: list[str]
|
||||
result: '{{ ["a", "b", "c"] is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# List of dictionaries.
|
||||
dtype: list[dict]
|
||||
result: '{{ [{"a": 1}, {"b": 2}] is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Dictionary. All keys are strings. All values are integers.
|
||||
dtype: dict[str, int]
|
||||
result: '{{ {"a": 1} is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Dictionary. All keys are strings. All values are integers.
|
||||
dtype: dict[str, int]
|
||||
result: '{{ {"a": 1, "b": 2} is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Type of strings is AnsibleUnicode or str
|
||||
# ----------------------------------------
|
||||
|
||||
# Dictionary. The keys are integers or strings. All values are strings.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
dtype: dict[int|str, str]
|
||||
data: {1: 'a', 'b': 'b'}
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
|
||||
# Dictionary. All keys are integers. All values are keys.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
dtype: dict[int, str]
|
||||
data: {1: 'a', 2: 'b'}
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
|
||||
# Dictionary. All keys are strings. Multiple types values.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
dtype: dict[str, bool|dict|float|int|list|str]
|
||||
data: {'a': 1, 'b': 1.1, 'c': 'abc', 'd': True, 'e': ['x', 'y', 'z'], 'f': {'x': 1, 'y': 2}}
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
|
||||
# List. Multiple types items.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
dtype: list[bool|dict|float|int|list|str]
|
||||
data: [1, 2, 1.1, 'abc', True, ['x', 'y', 'z'], {'x': 1, 'y': 2}]
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
|
||||
# Option dtype is list
|
||||
# --------------------
|
||||
|
||||
# AnsibleUnicode or str
|
||||
dtype: ['AnsibleUnicode', 'str']
|
||||
data: abc
|
||||
result: '{{ data is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# float or int
|
||||
dtype: ['float', 'int']
|
||||
data: 123
|
||||
result: '{{ data is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# float or int
|
||||
dtype: ['float', 'int']
|
||||
data: 123.45
|
||||
result: '{{ data is community.general.ansible_type(dtype) }}'
|
||||
# result => true
|
||||
|
||||
# Multiple alias
|
||||
# --------------
|
||||
|
||||
# int alias number
|
||||
alias: {"int": "number", "float": "number"}
|
||||
dtype: number
|
||||
data: 123
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
|
||||
# float alias number
|
||||
alias: {"int": "number", "float": "number"}
|
||||
dtype: number
|
||||
data: 123.45
|
||||
result: '{{ data is community.general.ansible_type(dtype, alias) }}'
|
||||
# result => true
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: Whether the data type is valid.
|
||||
type: bool
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common._collections_compat import Sequence
|
||||
from ansible_collections.community.general.plugins.plugin_utils.ansible_type import _ansible_type
|
||||
|
||||
|
||||
def ansible_type(data, dtype, alias=None):
|
||||
"""Validates data type"""
|
||||
|
||||
if not isinstance(dtype, Sequence):
|
||||
msg = "The argument dtype must be a string or a list. dtype is %s."
|
||||
raise AnsibleFilterError(msg % (dtype, type(dtype)))
|
||||
|
||||
if isinstance(dtype, str):
|
||||
data_types = [dtype]
|
||||
else:
|
||||
data_types = dtype
|
||||
|
||||
return _ansible_type(data, alias) in data_types
|
||||
|
||||
|
||||
class TestModule(object):
|
||||
|
||||
def tests(self):
|
||||
return {
|
||||
'ansible_type': ansible_type
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
# Copyright (c) 2023, Vladimir Botka <vbotka@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
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six import raise_from
|
||||
|
||||
try:
|
||||
from fqdn import FQDN
|
||||
except ImportError as imp_exc:
|
||||
ANOTHER_LIBRARY_IMPORT_ERROR = imp_exc
|
||||
else:
|
||||
ANOTHER_LIBRARY_IMPORT_ERROR = None
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: fqdn_valid
|
||||
short_description: Validates fully-qualified domain names against RFC 1123
|
||||
version_added: 8.1.0
|
||||
author: Vladimir Botka (@vbotka)
|
||||
requirements:
|
||||
- fqdn>=1.5.1 (PyPI)
|
||||
description:
|
||||
- This test validates Fully Qualified Domain Names (FQDNs)
|
||||
conforming to the Internet Engineering Task Force specification
|
||||
RFC 1123 and RFC 952.
|
||||
- The design intent is to validate that a string would be
|
||||
traditionally acceptable as a public Internet hostname to
|
||||
RFC-conforming software, which is a strict subset of the logic
|
||||
in modern web browsers like Mozilla Firefox and Chromium that
|
||||
determines whether make a DNS lookup.
|
||||
- Certificate Authorities like Let's Encrypt run a narrower set of
|
||||
string validation logic to determine validity for issuance. This
|
||||
test is not intended to achieve functional parity with CA
|
||||
issuance.
|
||||
- Single label names are allowed by default (O(min_labels=1)).
|
||||
options:
|
||||
_input:
|
||||
description: Name of the host.
|
||||
type: str
|
||||
required: true
|
||||
min_labels:
|
||||
description: Required minimum of labels, separated by period.
|
||||
default: 1
|
||||
type: int
|
||||
required: false
|
||||
allow_underscores:
|
||||
description: Allow underscore characters.
|
||||
default: false
|
||||
type: bool
|
||||
required: false
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Make sure that hostname is valid
|
||||
ansible.builtin.assert:
|
||||
that: hostname is community.general.fqdn_valid
|
||||
|
||||
- name: Make sure that hostname is at least 3 labels long (a.b.c)
|
||||
ansible.builtin.assert:
|
||||
that: hostname is community.general.fqdn_valid(min_labels=3)
|
||||
|
||||
- name: Make sure that hostname is at least 2 labels long (a.b). Allow '_'
|
||||
ansible.builtin.assert:
|
||||
that: hostname is community.general.fqdn_valid(min_labels=2, allow_underscores=True)
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: Whether the name is valid.
|
||||
type: bool
|
||||
'''
|
||||
|
||||
|
||||
def fqdn_valid(name, min_labels=1, allow_underscores=False):
|
||||
"""
|
||||
Example:
|
||||
- 'srv.example.com' is community.general.fqdn_valid
|
||||
- 'foo_bar.example.com' is community.general.fqdn_valid(allow_underscores=True)
|
||||
"""
|
||||
|
||||
if ANOTHER_LIBRARY_IMPORT_ERROR:
|
||||
raise_from(
|
||||
AnsibleError('Python package fqdn must be installed to use this test.'),
|
||||
ANOTHER_LIBRARY_IMPORT_ERROR
|
||||
)
|
||||
|
||||
fobj = FQDN(name, min_labels=min_labels, allow_underscores=allow_underscores)
|
||||
return (fobj.is_valid)
|
||||
|
||||
|
||||
class TestModule(object):
|
||||
''' Ansible test hostname validity.
|
||||
https://pypi.org/project/fqdn/
|
||||
'''
|
||||
|
||||
def tests(self):
|
||||
return {
|
||||
'fqdn_valid': fqdn_valid,
|
||||
}
|
Reference in New Issue
Block a user