Commits vergleichen
Keine gemeinsamen Commits. "stable" und "v0.1.0" haben vollständig unterschiedliche Historien.
|
@ -1,57 +0,0 @@
|
|||
============================
|
||||
Sebastian.Base Release Notes
|
||||
============================
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v0.4.2
|
||||
======
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- removed the empty options dict
|
||||
|
||||
v0.4.1
|
||||
======
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- added an default Display to the module
|
||||
- fixed the docification of dictionaries
|
||||
|
||||
v0.4.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
to prevent empty sections, the install and header methods return None if the method would just the scetion
|
||||
header
|
||||
|
||||
|
||||
v0.3.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
removed forgotten print calls
|
||||
|
||||
v0.3.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
rewrote the Types helper
|
||||
|
||||
v0.2.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
change the module to an ansible module
|
26
Makefile
26
Makefile
|
@ -1,26 +0,0 @@
|
|||
VERSION := $(shell hatch version)
|
||||
|
||||
format:
|
||||
black .
|
||||
isort .
|
||||
|
||||
version:
|
||||
@yq --yaml-output-grammar-version 1.2 -i -y -s --arg 'version' "${VERSION}" '.[0] | (.version = $$version)' galaxy.yml
|
||||
|
||||
changelog: version
|
||||
antsibull-changelog generate
|
||||
|
||||
docs: format
|
||||
update-doc
|
||||
|
||||
clean-dist:
|
||||
rm -rf dist
|
||||
|
||||
hatch-release: clean-dist version
|
||||
hatch build
|
||||
|
||||
galaxy-release: clean-dist changelog version
|
||||
ansible-galaxy collection build --output-path dist/galaxy
|
||||
|
||||
upload: galaxy-release hatch-release
|
||||
./upload.sh
|
22
README.md
22
README.md
|
@ -1,3 +1,21 @@
|
|||
# Ansible Collection - sebastian.base
|
||||
# Ansible module
|
||||
|
||||
Documentation for the collection.
|
||||
[![PyPI - Version](https://img.shields.io/pypi/v/ansible-module.svg)](https://pypi.org/project/ansible-module)
|
||||
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ansible-module.svg)](https://pypi.org/project/ansible-module)
|
||||
|
||||
-----
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [Installation](#installation)
|
||||
- [License](#license)
|
||||
|
||||
## Installation
|
||||
|
||||
```console
|
||||
pip install ansible-module
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
`ansible-module` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
objects:
|
||||
role: {}
|
||||
plugins:
|
||||
become: {}
|
||||
cache: {}
|
||||
callback: {}
|
||||
cliconf: {}
|
||||
connection: {}
|
||||
filter: {}
|
||||
httpapi: {}
|
||||
inventory: {}
|
||||
lookup: {}
|
||||
module: {}
|
||||
netconf: {}
|
||||
shell: {}
|
||||
strategy: {}
|
||||
test: {}
|
||||
vars: {}
|
||||
version: 0.4.2
|
|
@ -1,47 +0,0 @@
|
|||
ancestor: null
|
||||
releases:
|
||||
0.2.0:
|
||||
changes:
|
||||
release_summary: change the module to an ansible module
|
||||
fragments:
|
||||
- base_release.yml
|
||||
release_date: '2024-02-11'
|
||||
0.3.0:
|
||||
changes:
|
||||
release_summary: rewrote the Types helper
|
||||
fragments:
|
||||
- types.yml
|
||||
release_date: '2024-02-24'
|
||||
0.3.1:
|
||||
changes:
|
||||
release_summary: removed forgotten print calls
|
||||
fragments:
|
||||
- print_calls.yml
|
||||
release_date: '2024-02-24'
|
||||
0.4.0:
|
||||
changes:
|
||||
release_summary: 'to prevent empty sections, the install and header methods
|
||||
return None if the method would just the scetion
|
||||
|
||||
header
|
||||
|
||||
'
|
||||
fragments:
|
||||
- sectioning.yml
|
||||
release_date: '2024-03-08'
|
||||
0.4.1:
|
||||
changes:
|
||||
minor_changes:
|
||||
- added an default Display to the module
|
||||
- fixed the docification of dictionaries
|
||||
fragments:
|
||||
- display.yml
|
||||
- options_fix.yml
|
||||
release_date: '2024-03-13'
|
||||
0.4.2:
|
||||
changes:
|
||||
minor_changes:
|
||||
- removed the empty options dict
|
||||
fragments:
|
||||
- empty_options.yml
|
||||
release_date: '2024-03-13'
|
|
@ -1,32 +0,0 @@
|
|||
changelog_filename_template: ../CHANGELOG.rst
|
||||
changelog_filename_version_depth: 0
|
||||
changes_file: changelog.yaml
|
||||
changes_format: combined
|
||||
ignore_other_fragment_extensions: true
|
||||
keep_fragments: true
|
||||
mention_ancestor: true
|
||||
new_plugins_after_name: removed_features
|
||||
notesdir: fragments
|
||||
prelude_section_name: release_summary
|
||||
prelude_section_title: Release Summary
|
||||
sanitize_changelog: true
|
||||
sections:
|
||||
- - major_changes
|
||||
- Major Changes
|
||||
- - minor_changes
|
||||
- Minor Changes
|
||||
- - breaking_changes
|
||||
- Breaking Changes / Porting Guide
|
||||
- - deprecated_features
|
||||
- Deprecated Features
|
||||
- - removed_features
|
||||
- Removed Features (previously deprecated)
|
||||
- - security_fixes
|
||||
- Security Fixes
|
||||
- - bugfixes
|
||||
- Bugfixes
|
||||
- - known_issues
|
||||
- Known Issues
|
||||
title: Sebastian.Base
|
||||
trivial_section_name: trivial
|
||||
use_fqcn: true
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
minor_changes:
|
||||
- Added an type hint to the update_doc function
|
||||
- Added Type hint to the dictionary argument of the diff method
|
||||
- added an wrapper for fail_json and exit_json
|
||||
- improved the version update, the version is now only in one place the source of truth
|
|
@ -1,2 +0,0 @@
|
|||
---
|
||||
release_summary: change the module to an ansible module
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
minor_changes:
|
||||
- modspec now supports supports_check_mode and add_file_common_args
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
minor_changes:
|
||||
- added an default Display to the module
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
minor_changes:
|
||||
- removed the empty options dict
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
minor_changes:
|
||||
- fixed the docification of dictionaries
|
|
@ -1,2 +0,0 @@
|
|||
---
|
||||
release_summary: removed forgotten print calls
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
release_summary: |
|
||||
to prevent empty sections, the install and header methods return None if the method would just the scetion
|
||||
header
|
|
@ -1,2 +0,0 @@
|
|||
---
|
||||
release_summary: rewrote the Types helper
|
28
galaxy.yml
28
galaxy.yml
|
@ -1,28 +0,0 @@
|
|||
namespace: sebastian
|
||||
name: base
|
||||
version: 0.4.3
|
||||
readme: README.md
|
||||
authors:
|
||||
- Sebastian Tobie
|
||||
description: The base of my ansible collections. It provides the nessesary tools for
|
||||
my modules
|
||||
license_file: LICENSE.txt
|
||||
tags:
|
||||
- linux
|
||||
- systemd
|
||||
dependencies: {}
|
||||
repository: https://gitea.sebastian-tobie.de/ansible/ansible-module.git
|
||||
homepage: https://gitea.sebastian-tobie.de/ansible/ansible-module
|
||||
issues: https://gitea.sebastian-tobie.de/ansible/ansible-module/issues
|
||||
build_ignore:
|
||||
- '*.gz'
|
||||
- .*
|
||||
- Makefile
|
||||
- pyproject.toml
|
||||
- upload.sh
|
||||
- htmlcov
|
||||
- changelogs
|
||||
- docs
|
||||
- src
|
||||
- coverage.*
|
||||
- dist
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
# Collections must specify a minimum required ansible version to upload
|
||||
# to galaxy
|
||||
requires_ansible: '>=2.9.10'
|
|
@ -1,31 +0,0 @@
|
|||
# Collections Plugins Directory
|
||||
|
||||
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
|
||||
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
|
||||
would contain module utils and modules respectively.
|
||||
|
||||
Here is an example directory of the majority of plugins currently supported by Ansible:
|
||||
|
||||
```
|
||||
└── plugins
|
||||
├── action
|
||||
├── become
|
||||
├── cache
|
||||
├── callback
|
||||
├── cliconf
|
||||
├── connection
|
||||
├── filter
|
||||
├── httpapi
|
||||
├── inventory
|
||||
├── lookup
|
||||
├── module_utils
|
||||
├── modules
|
||||
├── netconf
|
||||
├── shell
|
||||
├── strategy
|
||||
├── terminal
|
||||
├── test
|
||||
└── vars
|
||||
```
|
||||
|
||||
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.16/plugins/plugins.html).
|
|
@ -1,175 +0,0 @@
|
|||
import pathlib
|
||||
import warnings
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, Union
|
||||
|
||||
__all__ = (
|
||||
"Types",
|
||||
"SYSTEMD_SERVICE_CONFIG",
|
||||
"SYSTEMD_NETWORK_CONFIG",
|
||||
"SYSTEMD_CONFIG_ROOT",
|
||||
"systemdbool",
|
||||
"AnsibleParameter",
|
||||
)
|
||||
|
||||
|
||||
SYSTEMD_CONFIG_ROOT = pathlib.Path("/etc/systemd")
|
||||
SYSTEMD_NETWORK_CONFIG = SYSTEMD_CONFIG_ROOT / "network"
|
||||
SYSTEMD_SERVICE_CONFIG = SYSTEMD_CONFIG_ROOT / "system"
|
||||
|
||||
AnsibleParameter = Dict[str, Any]
|
||||
|
||||
|
||||
def wrap_func(func, **updates):
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
attrs = frozenset(
|
||||
('__module__', '__name__', '__qualname__', '__doc__', '__annotations__', '__type_params__')
|
||||
) - frozenset(updates.keys())
|
||||
for attr in attrs:
|
||||
try:
|
||||
value = getattr(func, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
setattr(wrapper, attr, value)
|
||||
for attr, value in updates.items():
|
||||
setattr(wrapper, attr, value)
|
||||
wrapper.__dict__.update(func.__dict__)
|
||||
setattr(wrapper, "__wrapped__", func)
|
||||
return wrapper
|
||||
|
||||
|
||||
GENERIC_DOC = """Returns an dictionary for the Ansible {type} type."""
|
||||
|
||||
|
||||
def default(name: str):
|
||||
def wrapped(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter:
|
||||
option: AnsibleParameter = dict(type=name, required=required)
|
||||
if choices is not None:
|
||||
option["choices"] = choices
|
||||
if default is not None:
|
||||
option["default"] = default
|
||||
if help is not None:
|
||||
option["description"] = help.split("\n")
|
||||
return option
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
class meta(type):
|
||||
def __new__(cls, clsname, bases, attrs):
|
||||
types = frozenset(
|
||||
(
|
||||
"str",
|
||||
"bool",
|
||||
"int",
|
||||
"float",
|
||||
"path",
|
||||
"raw",
|
||||
"jsonarg",
|
||||
"json",
|
||||
"bytes",
|
||||
"dict",
|
||||
"list",
|
||||
"bits",
|
||||
)
|
||||
)
|
||||
for attr in types - set(attrs.keys()):
|
||||
attrs[attr] = wrap_func(
|
||||
default(attr), __doc__=GENERIC_DOC.format(type=attr), __name__=attr, __qualname__=f"{clsname}.{attr}"
|
||||
)
|
||||
attrs["__slots__"] = ()
|
||||
return super().__new__(cls, clsname, bases, attrs)
|
||||
|
||||
|
||||
class Types(metaclass=meta):
|
||||
|
||||
def list( # type: ignore[misc]
|
||||
elements: Union[Type[object], str, AnsibleParameter],
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
) -> AnsibleParameter:
|
||||
"""Wrapper for the Ansible list type
|
||||
|
||||
Args:
|
||||
elements: The type of the elements
|
||||
required: if the item is absolutly required
|
||||
help: an helptext for the ansible-doc
|
||||
"""
|
||||
option: AnsibleParameter = dict(type="list", required=required)
|
||||
if not isinstance(elements, (str, dict)):
|
||||
option["elements"] = elements.__name__
|
||||
elif isinstance(elements, dict):
|
||||
option["elements"] = elements["type"]
|
||||
if elements["type"] == "dict":
|
||||
option["options"] = dict()
|
||||
for name, value in elements["option"].items():
|
||||
option["options"][name] = value
|
||||
if "description" not in option["options"][name]:
|
||||
warnings.warn( # pragma: nocover
|
||||
f"helptext of option {name} is unset."
|
||||
" Ansible requires suboptions to have an documentation"
|
||||
)
|
||||
if help is not None:
|
||||
option["description"] = help.split("\n")
|
||||
return option
|
||||
|
||||
def dict(required: bool = False, help: Optional[str] = None, **options: AnsibleParameter) -> AnsibleParameter: # type: ignore[misc]
|
||||
"""Wrapper for the Ansible dict type
|
||||
|
||||
Args:
|
||||
required: if the item is absolutly required
|
||||
help: an helptext for the ansible-doc
|
||||
options: The individual options that this parameter has
|
||||
"""
|
||||
option: AnsibleParameter = dict(type="dict", required=required)
|
||||
option["options"] = options
|
||||
if help is not None:
|
||||
option["description"] = help.split("\n")
|
||||
return option
|
||||
|
||||
|
||||
def systemdbool(b: Union[bool, str]) -> str:
|
||||
"""Converts values into things systemd can parse"""
|
||||
if b is True:
|
||||
return "yes"
|
||||
elif b is False:
|
||||
return "no"
|
||||
return b
|
||||
|
||||
|
||||
def modspec(
|
||||
argument_spec: Dict[str, Dict[str, Any]],
|
||||
mutually_exclusive: Sequence[Tuple[str, ...]] = (),
|
||||
required_together: Sequence[Tuple[str, ...]] = (),
|
||||
required_one_of: Sequence[Tuple[str, ...]] = (),
|
||||
required_if: Sequence[Union[Tuple[str, Any, Tuple[str, ...]], Tuple[str, Any, Tuple[str, ...], bool]]] = (),
|
||||
required_by: Dict[str, Union[str, Tuple[str, ...]]] = {},
|
||||
supports_check_mode: bool = False,
|
||||
add_file_common_args: bool = False,
|
||||
) -> Dict[str, Any]: # pragma: nocover
|
||||
return dict(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_together=required_together,
|
||||
required_one_of=required_one_of,
|
||||
required_if=required_if,
|
||||
required_by=required_by,
|
||||
add_file_common_args=add_file_common_args,
|
||||
supports_check_mode=supports_check_mode,
|
||||
)
|
||||
|
||||
|
||||
def joindict(*items: dict) -> dict:
|
||||
"""merges one or more dictionaries into one"""
|
||||
odict = dict()
|
||||
for item in items:
|
||||
for key, value in item.items():
|
||||
odict[key] = value
|
||||
return odict
|
|
@ -22,9 +22,8 @@ classifiers = [
|
|||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dependencies = []
|
||||
[tool.hatch.build]
|
||||
directory = "dist/python"
|
||||
dependencies = ["ansible-core>=2.9.10"]
|
||||
|
||||
[project.urls]
|
||||
Documentation = "https://github.com/unknown/ansible-module#readme"
|
||||
Issues = "https://github.com/unknown/ansible-module/issues"
|
||||
|
@ -33,9 +32,6 @@ Source = "https://github.com/unknown/ansible-module"
|
|||
[project.scripts]
|
||||
update-doc = "ansible_module.update_doc:main"
|
||||
|
||||
[tool.hatch.build.targets.wheel.force-include]
|
||||
"plugins/module_utils" = "ansible_module/module_utils"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/ansible_module/__init__.py"
|
||||
|
||||
|
@ -59,9 +55,6 @@ style = ["ruff {args:.}", "black --check --diff {args:.}"]
|
|||
fmt = ["black {args:.}", "ruff --fix {args:.}", "style"]
|
||||
all = ["style", "typing"]
|
||||
|
||||
[tool.hatch.publish.index.repos.private]
|
||||
url = "https://gitea.sebastian-tobie.de/api/packages/sebastian/pypi"
|
||||
|
||||
[tool.black]
|
||||
target-version = ["py37"]
|
||||
line-length = 120
|
||||
|
@ -139,30 +132,26 @@ ansible_module = ["src/ansible_module", "*/ansible-module/src/ansible_module"]
|
|||
tests = ["tests", "*/ansible-module/tests"]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"no cov",
|
||||
"if __name__ == .__main__.:",
|
||||
"if TYPE_CHECKING:",
|
||||
"pragma: nocover",
|
||||
]
|
||||
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:", "pragma: nocover"]
|
||||
|
||||
[tool.mypy]
|
||||
disable_error_code = "import-untyped"
|
||||
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["ansible", "ansible.module_utils", "ansible.module_utils.basic"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = [
|
||||
"--cov-report",
|
||||
"json",
|
||||
"--cov-report",
|
||||
"term-missing:skip-covered",
|
||||
"--cov-report", "json",
|
||||
"--cov-report", "term-missing:skip-covered",
|
||||
"--cov",
|
||||
]
|
||||
pythonpath = ["src"]
|
||||
required_plugins = ["pytest-cov", "pytest-isort", "pytest-mypy"]
|
||||
required_plugins = [
|
||||
"pytest-cov",
|
||||
"pytest-isort",
|
||||
"pytest-mypy"
|
||||
]
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.4.3"
|
||||
__version__ = "0.1.0"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from . import update_doc
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_doc.main()
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
import pathlib
|
||||
import warnings
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, Union
|
||||
|
||||
__all__ = (
|
||||
"Types",
|
||||
"SYSTEMD_SERVICE_CONFIG",
|
||||
"SYSTEMD_NETWORK_CONFIG",
|
||||
"SYSTEMD_CONFIG_ROOT",
|
||||
"systemdbool",
|
||||
"AnsibleParameter",
|
||||
)
|
||||
|
||||
|
||||
SYSTEMD_CONFIG_ROOT = pathlib.Path("/etc/systemd")
|
||||
SYSTEMD_NETWORK_CONFIG = SYSTEMD_CONFIG_ROOT / "network"
|
||||
SYSTEMD_SERVICE_CONFIG = SYSTEMD_CONFIG_ROOT / "system"
|
||||
|
||||
AnsibleParameter = Dict[str, Any]
|
||||
|
||||
|
||||
class _Type:
|
||||
def __dir__(self) -> tuple: # pragma: nocover
|
||||
return (
|
||||
"str",
|
||||
"bool",
|
||||
"int",
|
||||
"float",
|
||||
"path",
|
||||
"raw",
|
||||
"jsonarg",
|
||||
"json",
|
||||
"bytes",
|
||||
"dict",
|
||||
"list",
|
||||
"bits",
|
||||
"__doc__",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str: # pragma: nocover
|
||||
return "Types()"
|
||||
|
||||
def __call__(self) -> "_Type": # pragma: nocover
|
||||
return self
|
||||
|
||||
def list(
|
||||
self,
|
||||
elements: Union[Type[object], str, AnsibleParameter],
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
) -> AnsibleParameter:
|
||||
option: AnsibleParameter = dict(type="list", required=required)
|
||||
if not isinstance(elements, (str, dict)):
|
||||
option["elements"] = elements.__name__
|
||||
elif isinstance(elements, dict):
|
||||
option["elements"] = elements["type"]
|
||||
if elements["type"] == "dict":
|
||||
option["options"] = dict()
|
||||
for name, value in elements["option"].items():
|
||||
option["options"][name] = value
|
||||
if "description" not in option["options"][name]:
|
||||
warnings.warn( # pragma: nocover
|
||||
f"helptext of option {name} is unset."
|
||||
" Ansible requires suboptions to have an documentation"
|
||||
)
|
||||
if help is not None:
|
||||
option["description"] = help.split("\n")
|
||||
return option
|
||||
|
||||
def dict(self, required: bool = False, help: Optional[str] = None, **options: dict) -> AnsibleParameter:
|
||||
option: AnsibleParameter = dict(type="dict", required=required)
|
||||
option["option"] = options
|
||||
if help is not None:
|
||||
option["description"] = help.split("\n")
|
||||
return option
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
def argument(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter:
|
||||
option: AnsibleParameter = dict(type=name, required=required)
|
||||
if choices is not None:
|
||||
option["choices"] = choices
|
||||
if default is not None:
|
||||
option["default"] = default
|
||||
if help is not None:
|
||||
option["description"] = help.split("\n")
|
||||
return option
|
||||
|
||||
argument.__name__ = name
|
||||
argument.__qualname__ = f"Types.{name}"
|
||||
argument.__doc__ = f"Simple wrapper for Ansible {name} argument dict"
|
||||
return argument
|
||||
|
||||
|
||||
Types = _Type()
|
||||
|
||||
|
||||
def systemdbool(b: Union[bool, str]) -> str:
|
||||
"""Converts values into things systemd can parse"""
|
||||
if b is True:
|
||||
return "yes"
|
||||
elif b is False:
|
||||
return "no"
|
||||
return b
|
||||
|
||||
|
||||
def modspec(
|
||||
argument_spec: Dict[str, Dict[str, Any]],
|
||||
mutually_exclusive: Sequence[Tuple[str, ...]] = (),
|
||||
required_together: Sequence[Tuple[str, ...]] = (),
|
||||
required_one_of: Sequence[Tuple[str, ...]] = (),
|
||||
required_if: Sequence[Union[Tuple[str, Any, Tuple[str, ...]], Tuple[str, Any, Tuple[str, ...], bool]]] = (),
|
||||
required_by: Dict[str, Union[str, Tuple[str, ...]]] = {},
|
||||
) -> Dict[str, Any]: # pragma: nocover
|
||||
return dict(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_together=required_together,
|
||||
required_one_of=required_one_of,
|
||||
required_if=required_if,
|
||||
required_by=required_by,
|
||||
)
|
||||
|
||||
|
||||
def joindict(*items: dict) -> dict:
|
||||
"""merges one or more dictionaries into one"""
|
||||
odict = dict()
|
||||
for item in items:
|
||||
for key, value in item.items():
|
||||
odict[key] = value
|
||||
return odict
|
|
@ -0,0 +1,76 @@
|
|||
from typing import Any, Dict, Optional, Type, Union
|
||||
|
||||
from _typeshed import Incomplete
|
||||
|
||||
SYSTEMD_CONFIG_ROOT: Incomplete
|
||||
SYSTEMD_NETWORK_CONFIG: Incomplete
|
||||
SYSTEMD_SERVICE_CONFIG: Incomplete
|
||||
AnsibleParameter = Dict[str, Any]
|
||||
|
||||
class Types:
|
||||
def list(
|
||||
elements: Union[Type[object], str, AnsibleParameter], required: bool = False, help: Optional[str] = None
|
||||
) -> AnsibleParameter: ...
|
||||
def dict(required: bool = False, help: Optional[str] = None, **options: dict) -> AnsibleParameter: ...
|
||||
def str(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def bool(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def int(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def float(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def path(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def raw(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def jsonarg(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def json(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def bytes(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
def bits(
|
||||
required: bool = False,
|
||||
help: Optional[str] = None,
|
||||
choices: Optional[Sequence] = None,
|
||||
default: Optional[Any] = None,
|
||||
) -> AnsibleParameter: ...
|
||||
|
||||
def systemdbool(b: Union[bool, str]) -> str: ...
|
|
@ -1,11 +1,11 @@
|
|||
import pathlib
|
||||
from copy import deepcopy
|
||||
from typing import Any, Callable, ClassVar, Dict, NoReturn, Optional, Type, TypeVar, Union, overload, TypedDict
|
||||
from ansible.utils.display import Display
|
||||
from typing import (Any, Callable, ClassVar, Dict, NoReturn, Optional, Type,
|
||||
TypeVar, Union, overload)
|
||||
|
||||
import ansible.module_utils.basic as basic
|
||||
|
||||
from .generic import AnsibleParameter, Types, systemdbool
|
||||
from ansible_module.generic import AnsibleParameter, Types, systemdbool
|
||||
|
||||
__all__ = (
|
||||
"AnsibleModule",
|
||||
|
@ -18,13 +18,6 @@ __all__ = (
|
|||
T = TypeVar("T")
|
||||
|
||||
|
||||
class TypedDiff(TypedDict):
|
||||
before: str
|
||||
after: str
|
||||
before_header: Optional[str] = None
|
||||
after_header: Optional[str] = None
|
||||
|
||||
|
||||
def docify(input: Union[dict, AnsibleParameter]) -> dict:
|
||||
options = dict()
|
||||
for name, help in input.items():
|
||||
|
@ -43,9 +36,9 @@ def docify(input: Union[dict, AnsibleParameter]) -> dict:
|
|||
options[name]["default"] = []
|
||||
if "default" in help:
|
||||
options[name]["default"] = help["default"]
|
||||
if "options" in help and help["options"] != {}:
|
||||
if "options" in help:
|
||||
options[name]["options"] = docify(help["options"])
|
||||
if "choices" in help and len(help["choices"]) > 0:
|
||||
if "choices" in help:
|
||||
options[name]["choices"] = tuple(help["choices"])
|
||||
return options
|
||||
|
||||
|
@ -63,8 +56,6 @@ class AnsibleModule(object):
|
|||
module_spec: ClassVar[dict]
|
||||
#: This is set by classes that define common things for their subclasses, like behaviour of the run and check methods. This is used by `SystemdUnitModule`
|
||||
_common_args: ClassVar[dict[str, Any]] = dict()
|
||||
#: For log messages
|
||||
display: Display
|
||||
|
||||
@property
|
||||
def params(self) -> Dict[str, Any]:
|
||||
|
@ -80,7 +71,6 @@ class AnsibleModule(object):
|
|||
specs["argument_spec"].update(modspec["argument_spec"])
|
||||
del modspec["argument_spec"]
|
||||
specs.update(modspec)
|
||||
self.display = Display()
|
||||
self.module = basic.AnsibleModule(**specs)
|
||||
self.tmpdir = pathlib.Path(self.module.tmpdir)
|
||||
|
||||
|
@ -89,7 +79,7 @@ class AnsibleModule(object):
|
|||
self.result[key] = value
|
||||
|
||||
@overload
|
||||
def diff(self, diff: TypedDiff): # pragma: nocover
|
||||
def diff(self, diff: Dict[str, str]): # pragma: nocover
|
||||
pass
|
||||
|
||||
@overload
|
||||
|
@ -168,8 +158,12 @@ class AnsibleModule(object):
|
|||
except Exception as exc:
|
||||
import traceback
|
||||
|
||||
self.fail("".join(traceback.format_exception(type(exc), exc, exc.__traceback__)))
|
||||
self.exit()
|
||||
self.module.fail_json(
|
||||
"".join(traceback.format_exception(type(exc), exc, exc.__traceback__)),
|
||||
**self.result,
|
||||
)
|
||||
self.module.exit_json(**self.result)
|
||||
raise Exception("exit_json failed")
|
||||
|
||||
@classmethod
|
||||
def doc(cls) -> str:
|
||||
|
@ -201,14 +195,6 @@ class AnsibleModule(object):
|
|||
)
|
||||
)
|
||||
|
||||
def fail(self, message: str) -> NoReturn:
|
||||
"""Wrapper for AnsibleModule.fail_json"""
|
||||
self.module.fail_json(message, **self.result)
|
||||
|
||||
def exit(self) -> NoReturn:
|
||||
"""Wrapper for AnsibleModule.exit_json"""
|
||||
self.module.exit_json(**self.result)
|
||||
|
||||
|
||||
class SystemdUnitModule(AnsibleModule):
|
||||
#: path of the unitfile managed by this module
|
||||
|
@ -243,37 +229,30 @@ class SystemdUnitModule(AnsibleModule):
|
|||
),
|
||||
)
|
||||
#: if defined it will be called after run has changed the unitfile
|
||||
post: Optional[Callable[[], None]] = None
|
||||
post: Optional[Callable[[], None]]
|
||||
|
||||
#: generates the install section if the unit is installable
|
||||
install: ClassVar[Optional[Callable[["SystemdUnitModule"], str | None]]] = None
|
||||
install: ClassVar[Optional[Callable[["SystemdUnitModule"], str]]]
|
||||
|
||||
def unit(self) -> str: # pragma: nocover
|
||||
raise NotImplementedError()
|
||||
|
||||
def header(self) -> Optional[str]:
|
||||
parts = self.map_param(
|
||||
description="Description",
|
||||
documentation="Documentation",
|
||||
requires="Requires",
|
||||
wants="Wants",
|
||||
partof="PartOf",
|
||||
before="Before",
|
||||
after="After",
|
||||
def header(self) -> str:
|
||||
header = "[Unit]\n"
|
||||
header += "".join(
|
||||
self.map_param(
|
||||
description="Description",
|
||||
documentation="Documentation",
|
||||
requires="Requires",
|
||||
wants="Wants",
|
||||
partof="PartOf",
|
||||
before="Before",
|
||||
after="After",
|
||||
)
|
||||
)
|
||||
if len(parts) == 0:
|
||||
return None
|
||||
header = "[Unit]\n" + "".join(parts)
|
||||
return header
|
||||
|
||||
def _unit(self, *parts: Optional[str]) -> str:
|
||||
opart = []
|
||||
for part in parts:
|
||||
if part is not None:
|
||||
opart.append(part)
|
||||
return "\n".join(opart)
|
||||
|
||||
def map_param(self, **parammap: str) -> list[str]:
|
||||
def map_param(self, **parammap: str):
|
||||
"""maps an dict with keys for an section with given params. The key of the dictionary is the parameter and the value is the key in the unitfile. If an parameter has multiple values it adds multiple entries"""
|
||||
output: list[str] = []
|
||||
for param, key in parammap.items():
|
||||
|
@ -285,7 +264,7 @@ class SystemdUnitModule(AnsibleModule):
|
|||
output.append(f"{key}={systemdbool(params)}\n")
|
||||
return output
|
||||
|
||||
def unitfile_gen(self) -> None: # pragma: nocover
|
||||
def unitfile_gen(self): # pragma: nocover
|
||||
path = self.tmpdir / "newunit"
|
||||
with open(path, "w") as unit:
|
||||
unit.write(self.unit())
|
||||
|
@ -293,7 +272,7 @@ class SystemdUnitModule(AnsibleModule):
|
|||
self.module.set_group_if_different(path.as_posix(), "root", False)
|
||||
self.module.set_mode_if_different(path.as_posix(), "0644", False)
|
||||
if self.unitfile.exists():
|
||||
diff: dict[str, str] = dict()
|
||||
diff = dict()
|
||||
self.changed = self.changed | self.module.set_owner_if_different(
|
||||
self.unitfile.as_posix(),
|
||||
"root",
|
||||
|
@ -364,15 +343,13 @@ def installable(_class: Type[SystemdUnitModule]): # pragma: nocover
|
|||
)
|
||||
specs["argument_spec"].update(arguments)
|
||||
|
||||
def install(self: SystemdUnitModule) -> Optional[str]:
|
||||
parts = []
|
||||
def install(self: SystemdUnitModule) -> str:
|
||||
output = "[Install]\n"
|
||||
for argument, key in _INSTALL_MAPPING.items():
|
||||
if self.get(argument, False):
|
||||
for unit in self.get(argument): # type: ignore
|
||||
parts.append("{}={}\n".format(key, unit))
|
||||
if len(parts) == 0:
|
||||
return None
|
||||
return "[Install]\n" + "".join(parts)
|
||||
output += "{}={}\n".format(key, unit)
|
||||
return output
|
||||
|
||||
_class.install = install
|
||||
_class.module_spec = specs
|
|
@ -0,0 +1,60 @@
|
|||
import pathlib
|
||||
from typing import (Any, Callable, ClassVar, Dict, NoReturn, Optional, Type,
|
||||
TypeVar, overload)
|
||||
|
||||
import ansible.module_utils.basic as basic
|
||||
from _typeshed import Incomplete
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class AnsibleModule:
|
||||
name: ClassVar[str]
|
||||
module: basic.AnsibleModule
|
||||
result: dict
|
||||
module_spec: ClassVar[dict]
|
||||
@property
|
||||
def params(self) -> Dict[str, Any]: ...
|
||||
tmpdir: Incomplete
|
||||
def __init__(self) -> None: ...
|
||||
def set(self, key: str, value): ...
|
||||
@overload
|
||||
def diff(self, diff: Dict[str, str]): ...
|
||||
@overload
|
||||
def diff(
|
||||
self,
|
||||
before: Optional[str] = ...,
|
||||
after: Optional[str] = ...,
|
||||
before_header: Optional[str] = ...,
|
||||
after_header: Optional[str] = ...,
|
||||
): ...
|
||||
def get(self, key: str, default: Optional[T] = ...) -> T: ...
|
||||
def changed_get(self): ...
|
||||
def changed_set(self, value) -> None: ...
|
||||
changed: Incomplete
|
||||
def prepare(self) -> None: ...
|
||||
def check(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
def __call__(self) -> NoReturn: ...
|
||||
@classmethod
|
||||
def doc(cls) -> str: ...
|
||||
|
||||
class SystemdUnitModule(AnsibleModule):
|
||||
unitfile: pathlib.Path
|
||||
post: Optional[Callable[[], None]]
|
||||
install: ClassVar[Optional[Callable[[SystemdUnitModule], str]]]
|
||||
def unit(self) -> str: ...
|
||||
def header(self) -> str: ...
|
||||
def map_param(self, **parammap: str): ...
|
||||
changed: Incomplete
|
||||
def unitfile_gen(self) -> None: ...
|
||||
def check(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
def installable(_class: Type[SystemdUnitModule]): ...
|
||||
|
||||
class SystemdReloadMixin:
|
||||
module: basic.AnsibleModule
|
||||
unitfile: pathlib.Path
|
||||
restartable: bool
|
||||
changed: bool
|
||||
def post(self) -> None: ...
|
|
@ -10,13 +10,8 @@ moduledir = pathlib.Path("plugins/modules")
|
|||
regex = re.compile("DOCUMENTATION *= *r?(?P<quote>\"{3}|'{3})(---)?.*?(?P=quote)", re.MULTILINE | re.DOTALL)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
modules = list(moduledir.iterdir())
|
||||
except:
|
||||
print("failed to find modules")
|
||||
return
|
||||
for modfile in modules:
|
||||
def main():
|
||||
for modfile in moduledir.iterdir():
|
||||
if modfile.name in ("__init__.py", "__pycache__", "unit.py.example"):
|
||||
continue
|
||||
mod = importlib.import_module(".".join((modfile.parts[:-1]) + (modfile.stem,)))
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# SPDX-FileCopyrightText: 2023-present Sebastian Tobie <sebastian@sebastian-tobie.de>
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
|
@ -0,0 +1,57 @@
|
|||
import os
|
||||
import unittest
|
||||
|
||||
from ansible_module.generic import Types, joindict, systemdbool
|
||||
|
||||
|
||||
class TestTypes(unittest.TestCase):
|
||||
"""tests the Types class"""
|
||||
|
||||
def testsimpletype(self):
|
||||
"""this tests if an simple type is correctly build"""
|
||||
output = Types.str(required=True, help="test", choices=("a", "1"), default="1")
|
||||
self.assertIn("type", output)
|
||||
self.assertIn("required", output)
|
||||
self.assertIn("default", output)
|
||||
self.assertIn("choices", output)
|
||||
self.assertEqual(output["type"], "str")
|
||||
self.assertEqual(Types.str.__name__, "str")
|
||||
self.assertEqual(output["required"], True)
|
||||
self.assertEqual(output["default"], "1")
|
||||
self.assertTupleEqual(output["choices"], ("a", "1"))
|
||||
Types.str()
|
||||
|
||||
def testlisttype(self):
|
||||
"""this tests if the special type list is correctly build"""
|
||||
output = Types.list(str)
|
||||
Types.list("str")
|
||||
self.assertIn("type", output)
|
||||
self.assertIn("elements", output)
|
||||
self.assertIn("required", output)
|
||||
self.assertEqual(output["type"], "list")
|
||||
self.assertEqual(output["required"], False)
|
||||
self.assertEqual(output["elements"], "str")
|
||||
Types.list(Types.dict(a=Types.str(help="")), help="")
|
||||
Types.list("str")
|
||||
|
||||
def testdicttype(self):
|
||||
output = Types.dict(help="HILFE")
|
||||
self.assertIn("type", output)
|
||||
self.assertIn("required", output)
|
||||
self.assertIn("description", output)
|
||||
self.assertIn("option", output)
|
||||
|
||||
|
||||
class TestFuncs(unittest.TestCase):
|
||||
def testsystemdbool(self):
|
||||
self.assertEqual("Text", systemdbool("Text"))
|
||||
self.assertEqual("no", systemdbool(False))
|
||||
self.assertEqual("yes", systemdbool(True))
|
||||
|
||||
def testjoindict(self):
|
||||
dicts = (
|
||||
dict(a=1, b=2),
|
||||
dict(b=3, c=4),
|
||||
)
|
||||
output = dict(a=1, b=3, c=4)
|
||||
self.assertDictEqual(output, joindict(*dicts))
|
|
@ -0,0 +1,71 @@
|
|||
import sys
|
||||
import unittest
|
||||
|
||||
from ansible_module.generic import modspec
|
||||
from ansible_module.module import SystemdUnitModule, docify
|
||||
|
||||
|
||||
class ModuleMock(SystemdUnitModule):
|
||||
name = "mock"
|
||||
module_spec = modspec({})
|
||||
|
||||
|
||||
class TestSystemdUnitModule(unittest.TestCase):
|
||||
def setUp(self):
|
||||
sys.argv = [sys.argv[0], '{"ANSIBLE_MODULE_ARGS":{}}']
|
||||
self.mod = ModuleMock()
|
||||
|
||||
def test_header(self):
|
||||
self.assertEqual("[Unit]\n", self.mod.header())
|
||||
|
||||
def test_doc(self):
|
||||
ModuleMock.doc()
|
||||
|
||||
def test_params(self):
|
||||
self.mod.module.params = dict(ok="ok", none=None, true=True, false=False, list=("item1", "item2"))
|
||||
self.assertEqual("ok", self.mod.get("ok"))
|
||||
self.assertEqual("default", self.mod.get("none", "default"))
|
||||
self.assertRaises(KeyError, self.mod.get, "none", None)
|
||||
self.assertListEqual(
|
||||
['TRUE=yes\n', "FALSE=no\n", "OK=ok\n", "LIST=item1\n", "LIST=item2\n"],
|
||||
self.mod.map_param(true="TRUE", false="FALSE", ok="OK", list="LIST"),
|
||||
)
|
||||
self.assertRaises(TypeError, self.mod.diff, diff=dict(), before=True, after=False)
|
||||
self.mod.diff(before="a", after="b", before_header="a", after_header="b")
|
||||
self.mod.diff(dict(before="c", after="d"))
|
||||
|
||||
def test_result(self):
|
||||
self.assertFalse(self.mod.changed)
|
||||
self.mod.changed = 5
|
||||
self.assertTrue(self.mod.changed)
|
||||
self.mod.set("key", "value")
|
||||
self.assertDictEqual(
|
||||
dict(
|
||||
changed=True,
|
||||
key="value",
|
||||
),
|
||||
self.mod.result,
|
||||
)
|
||||
|
||||
|
||||
class TestFunctions(unittest.TestCase):
|
||||
def test_docify(self):
|
||||
input = dict(
|
||||
item1=dict(type="str", description=""),
|
||||
item2=dict(required=True, type="int", default=5),
|
||||
item3=dict(type="list", required=False, elements="int"),
|
||||
item4=dict(type="list", required=True, elements="int"),
|
||||
item5=dict(type="str", choices=["a", "b", "c"]),
|
||||
item6=dict(type="dict", options=dict(a=dict(type="str"))),
|
||||
)
|
||||
expected = dict(
|
||||
item1=dict(required=False, type="str", description=[""]),
|
||||
item2=dict(required=True, type="int", default=5),
|
||||
item3=dict(type="list", required=False, elements="int", default=[]),
|
||||
item4=dict(type="list", required=True, elements="int"),
|
||||
item5=dict(type="str", required=False, choices=("a", "b", "c")),
|
||||
item6=dict(type="dict", required=False, options=dict(a=dict(type="str", required=False))),
|
||||
)
|
||||
output = docify(input)
|
||||
for key, value in input.items():
|
||||
self.assertDictEqual(expected[key], output[key])
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
user=$(yq -r .namespace galaxy.yml)
|
||||
package=$(yq -r .name galaxy.yml)
|
||||
version=$(yq -r .version galaxy.yml)
|
||||
printf "Namespace: %s\nPackage: %s\nVersion: %s\n" $user $package $version
|
||||
ansible-galaxy collection publish dist/galaxy/*
|
||||
hatch publish -r ansible dist/python/*
|
Laden…
In neuem Issue referenzieren