1
0
Fork 0

Commits vergleichen

..

Keine gemeinsamen Commits. "main" und "testing" haben vollständig unterschiedliche Historien.

25 geänderte Dateien mit 783 neuen und 1348 gelöschten Zeilen

Datei anzeigen

@ -14,5 +14,3 @@ insert_final_newline = true
[*.{yaml,yml}] [*.{yaml,yml}]
indent_size = 2 indent_size = 2
[Makefile]
indent_style = tab

Datei anzeigen

@ -1,114 +1,10 @@
===================================== =====================================
sebastian.systemd 0.4.3 Release Notes sebastian.systemd 0.1.3 Release Notes
===================================== =====================================
.. contents:: Topics .. contents:: Topics
v0.4.3
======
Changelog
---------
added new options to system_service
v0.4.2
======
Changelog
---------
upgraded to the new method used by sebastian.base
v0.4.0
======
Changelog
---------
added timer module
v0.3.8
======
Changelog
---------
fixed the import again.
v0.3.7
======
Changelog
---------
fixed the import path
v0.3.6
======
Changelog
---------
fixed the import of the module utils
v0.3.5
======
Changelog
---------
added some ignores and changed my name
v0.3.4
======
Changelog
---------
moved the module_utils to an indipendent repository
v0.3.3
======
Changelog
---------
added netdev module
v0.3.2
======
Changelog
---------
added virtualization and negative matches to link and network
v0.3.1
======
Changelog
---------
fixed runtime.yml
v0.3.0
======
Changelog
---------
added socket module and some small fixes
v0.2.0
======
Changelog
---------
added an rudimentary system_service module
v0.1.3 v0.1.3
====== ======

Datei anzeigen

@ -1,15 +0,0 @@
format:
black .
isort .
changelog:
antsibull-changelog generate
docs: format
update-doc
release: changelog docs
ansible-galaxy collection build --output-path dist
upload: release
./upload.sh

Datei anzeigen

@ -2,75 +2,37 @@ ancestor: null
releases: releases:
0.1.0: 0.1.0:
changes: changes:
release_date: '2023-04-15'
release_summary: Erstes Release
major_changes: major_changes:
- added systemd_link module - added systemd_link module
- added systemd_mount module - added systemd_mount module
- added systemd_network module - added systemd_network module
release_summary: Erstes Release modules:
release_date: "2023-04-15" - name: systemd_link
description: Erstellt eine Systemd Link Unit
- name: systemd_mount
description: Erstellt eine Systemd mount Unit
- name: systemd_network
description: Erstellt eine Systemd Network Unit
0.1.1: 0.1.1:
changes: changes:
minor_changes: release_date: '2023-04-15'
- "update_doc hinzugef\xFCgt. um Dokumentation automatisch zu aktualisieren"
release_summary: kleines feature update release_summary: kleines feature update
release_date: "2023-04-15" minor_changes:
- update_doc hinzugefügt. um Dokumentation automatisch zu aktualisieren
0.1.2: 0.1.2:
changes: changes:
release_summary: "Pfad der aufl\xF6sung f\xFCr Tests und autodoc ge\xE4ndert, weil Mitogen sonst blockiert" release_date: '2023-04-15'
release_date: "2023-04-15" release_summary: Pfad der auflösung für Tests und autodoc geändert, weil Mitogen sonst blockiert
0.1.3: 0.1.3:
changes: changes:
release_summary: renamed all modules to names without systemd_ prefix release_date: "2023-04-20"
release_date: "2023-04-20" release_summary: "renamed all modules to names without systemd_ prefix"
0.2.0: 0.2.0:
changes: changes:
release_summary: added an rudimentary system_service module release_date: "2023-04-21"
release_date: "2023-04-21" release_summary: "added an rudimentary system_service module"
0.3.0: modules:
changes: - name: system_service
release_summary: added socket module and some small fixes description: erstellt ein einfachen systemd system service
release_date: "2023-04-23"
0.3.1:
changes:
release_summary: fixed runtime.yml
release_date: "2023-04-23"
0.3.2:
changes:
release_summary: added virtualization and negative matches to link and network
release_date: "2023-07-15"
0.3.3:
changes:
release_summary: added netdev module
release_date: "2023-07-16"
0.3.4:
changes:
release_summary: moved the module_utils to an indipendent repository
release_date: "2023-11-26"
0.3.5:
changes:
release_summary: added some ignores and changed my name
release_date: "2023-12-30"
0.3.6:
changes:
release_summary: fixed the import of the module utils
release_date: "2024-02-11"
0.3.7:
changes:
release_summary: fixed the import path
release_date: "2024-02-11"
0.3.8:
changes:
release_summary: fixed the import again.
release_date: "2024-02-11"
0.4.0:
release_date: "2024-02-24"
changes:
release_summary: added timer module
0.4.2:
release_date: "2024-03-09"
changes:
release_summary: upgraded to the new method used by sebastian.base
0.4.3:
release_date: "2024-03-09"
changes:
release_summary: added new options to system_service

Datei anzeigen

@ -1,30 +1,21 @@
--- ---
namespace: sebastian namespace: sebastian
name: systemd name: systemd
version: 0.4.3 version: 0.1.2
readme: README.md readme: README.md
authors: authors:
- Sebastian Tobie - Sebastian Tobie
description: An simple for generating systemd units with ansible description: An simple for generating systemd units with ansible
license_file: "LICENSE" license_file: 'LICENSE'
tags: tags:
- systemd - systemd
- linux - linux
dependencies: dependencies: {}
sebastian.base: ">=0.4.3" repository: https://gitea.sebastian-tobie.de/sebastian/ansible-systemd.git
repository: https://gitea.sebastian-tobie.de/ansible/ansible-systemd.git
# documentation: http://docs.example.com # documentation: http://docs.example.com
homepage: https://gitea.sebastian-tobie.de/ansible/ansible-systemd homepage: https://gitea.sebastian-tobie.de/sebastian/ansible-systemd
issues: https://gitea.sebastian-tobie.de/ansible/ansible-systemd/issues issues: https://gitea.sebastian-tobie.de/sebastian/ansible-systemd/issues
build_ignore: build_ignore: []
- "*.gz"
- ".*"
- Makefile
- pyproject.toml
- upload.sh
- htmlcov
- changelogs
- docs
# manifest: null # manifest: null

Datei anzeigen

@ -1,4 +1,52 @@
--- ---
# Collections must specify a minimum required ansible version to upload # Collections must specify a minimum required ansible version to upload
# to galaxy # to galaxy
requires_ansible: '>=2.9.10' # requires_ansible: '>=2.9.10'
# Content that Ansible needs to load from another location or that has
# been deprecated/removed
# plugin_routing:
# action:
# redirected_plugin_name:
# redirect: ns.col.new_location
# deprecated_plugin_name:
# deprecation:
# removal_version: "4.0.0"
# warning_text: |
# See the porting guide on how to update your playbook to
# use ns.col.another_plugin instead.
# removed_plugin_name:
# tombstone:
# removal_version: "2.0.0"
# warning_text: |
# See the porting guide on how to update your playbook to
# use ns.col.another_plugin instead.
# become:
# cache:
# callback:
# cliconf:
# connection:
# doc_fragments:
# filter:
# httpapi:
# inventory:
# lookup:
# module_utils:
# modules:
# netconf:
# shell:
# strategy:
# terminal:
# test:
# vars:
# Python import statements that Ansible needs to load from another location
# import_redirection:
# ansible_collections.ns.col.plugins.module_utils.old_location:
# redirect: ansible_collections.ns.col.plugins.module_utils.new_location
# Groups of actions/modules that take a common set of options
# action_groups:
# group_name:
# - module1
# - module2

0
plugins/__init__.py Normale Datei
Datei anzeigen

Datei anzeigen

Datei anzeigen

@ -0,0 +1,128 @@
import pathlib
from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, Union
__all__ = (
"Types",
"SYSTEMD_SERVICE_CONFIG",
"SYSTEMD_NETWORK_CONFIG",
"SYSTEMD_CONFIG_ROOT",
)
SYSTEMD_CONFIG_ROOT = pathlib.Path("/etc/systemd")
SYSTEMD_NETWORK_CONFIG = SYSTEMD_CONFIG_ROOT / "network"
SYSTEMD_SERVICE_CONFIG = SYSTEMD_CONFIG_ROOT / "system"
class _sdict(dict):
_help: Optional[str]
__name__: str
class _Type(type):
def __new__(metacls, cls, bases, classdict, **kwds):
individual = dict()
virtfunc = None
virtual = ()
special = dict()
for key, value in classdict.items():
if key.startswith("_"):
if key == "__getattr__":
virtfunc = value
special[key] = value
elif key == "__dir__":
virtual = tuple(value(None))
special[key] = value
elif key in ("__doc__", "__module__", "__qualname__"):
special[key] = value
else:
individual[key] = value
if len(virtual) != 0 and virtfunc is None: # pragma: nocover
raise TypeError("Virtual funcs defined, but no func to generate them defined")
special["_attr"] = tuple(virtual + tuple(individual.keys()))
special["_vfunc"] = virtfunc
special["_virtual"] = virtual
special["_individual"] = individual
annotations = dict()
if len(virtual) != 0 and virtfunc is not None: # pragma: nocover
anno = virtfunc(None, virtual[0]).__annotations__
for virtualkey in virtual:
annotations[virtualkey] = Callable[[*anno.values()], Dict[str, Any]]
annotations["__dir__"] = Callable[[], Tuple[str]]
special["__annotations__"] = annotations
inst = super().__new__(metacls, cls, bases, special, **kwds)
return inst
def __getattribute__(self, __name: str) -> Any:
if __name in (
"__dict__",
"__doc__",
"_attr",
"__annotations__",
"_virtual",
"_vfunc",
"_individual",
):
return super().__getattribute__(__name)
if __name in self._virtual:
return self._vfunc(self, __name)
if __name in self._individual:
return self._individual[__name]
return super().__getattribute__(__name)
def __dir__(self):
data = set()
data.update(("__dir__", "__doc__", "__annotations__"))
data.update(self._virtual)
data.update(self._individual.keys())
return tuple(data)
class Types(metaclass=_Type):
"""Provides helpers for the ansible types"""
def list(
elements: Union[Type[object], str],
required: bool = False,
help: Optional[str] = None,
) -> dict:
if not isinstance(elements, str):
elements = elements.__name__
option = _sdict(type="list", elements=elements, required=required)
option._help = help
return option
def __dir__(self) -> tuple:
return (
"str",
"dict",
"bool",
"int",
"float",
"path",
"raw",
"jsonarg",
"json",
"bytes",
"bits",
)
def __getattr__(self, name: str):
def argument(
required: bool = False,
help: Optional[str] = None,
choices: Optional[Sequence] = None,
default: Optional[Any] = None,
):
"""Simple wrapper for Ansible {0} argument dict"""
output = _sdict(type=name, required=required)
if choices is not None:
output["choices"] = choices
if default is not None:
output["default"] = default
output._help = help
return output
argument.__name__ = name
argument.__doc__ = argument.__doc__.format(name)
return argument

315
plugins/module_utils/module.py Normale Datei
Datei anzeigen

@ -0,0 +1,315 @@
import pathlib
from typing import Any, Callable, ClassVar, Dict, NoReturn, Optional, Type, TypeVar, overload
import ansible.module_utils.basic as basic
try:
from ansible_collections.sebastian.systemd.plugins.module_utils.generic import Types, _sdict
except ImportError:
from plugins.module_utils.generic import Types, _sdict
__all__ = (
"AnsibleModule",
"SystemdUnitModule",
)
T = TypeVar("T")
class AnsibleModule(object):
"""Simple wrapper for the basic.AnsibleModule"""
#: name of the module. This is required for the generation of the Ansible documentation
name: ClassVar[str]
#: The AnsibleModule for this Module
module: basic.AnsibleModule
#: The result of this Module call. It always contains the changed key, so in any case an Module can report if it changed anything
result: dict
#: the specification of the arguments. Subclasses that are usable Modules must set this value.
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 = dict()
@property
def params(self) -> Dict[str, Any]:
"""params is an wrapper for the module.params"""
return self.module.params # type: ignore
def __init__(self):
self.result = dict(changed=False)
specs = dict()
specs.update(self._common_args)
modspec = self.module_spec.copy()
if "argument_spec" in modspec and "argument_spec" in self._common_args:
specs["argument_spec"].update(modspec["argument_spec"])
del modspec["argument_spec"]
specs.update(modspec)
self.module = basic.AnsibleModule(**specs)
self.tmpdir = pathlib.Path(self.module.tmpdir)
def set(self, key: str, value):
"""sets an value for the result"""
self.result[key] = value
@overload
def diff(self, diff: Dict[str, str]):
pass
def diff(
self,
diff: Optional[Dict[str, str]] = None,
*,
before: Optional[str] = None,
after: Optional[str] = None,
before_header: Optional[str] = None,
after_header: Optional[str] = None,
):
"""adds the special return value "diff". This allows Modules to present the changes of files to the caller. it takes care of the special semantics of the return value"""
if "diff" not in self.result:
self.result["diff"] = list()
if diff is not None and not any((before is not None, after is not None)):
pass
elif all((before is not None, after is not None, diff is None)):
diff = dict(
before=before,
after=after,
)
if before_header is not None:
diff["before_header"] = before_header
if after_header is not None:
diff["after_header"] = after_header
else:
raise TypeError("only diff or before and after can be set, not both of them")
self.result["diff"].append(diff)
def get(self, key: str, default: T = None) -> T:
"""returns an Parameter of the Module."""
if key not in self.params.keys():
return default
if self.params[key] is None and default is not None:
return default
if self.params[key] is None or key not in self.params:
raise KeyError()
return self.params[key]
def changed_get(self):
"""value that shows if changes were detected/made"""
return self.result["changed"]
def changed_set(self, value):
self.result["changed"] = not not value
changed = property(changed_get, changed_set, None, changed_get.__doc__)
def prepare(self):
raise NotImplementedError()
def check(self):
raise NotImplementedError()
def run(self):
raise NotImplementedError()
def __call__(self) -> NoReturn:
"""This calls the module. first prepare is called and then check or run, depending on the check mode.
If an exception is raised this is catched and the module automatically fails with an traceback
"""
self.prepare()
try:
if self.module.check_mode:
self.check()
else:
self.run()
except Exception as exc:
import traceback
self.module.fail_json(
"".join(traceback.format_exception(type(exc), exc, exc.__traceback__)),
**self.result,
)
self.module.exit_json(**self.result)
@classmethod
def doc(cls) -> str:
"""this returns the documentation string of the module. If the help arguments of an Types method was given, it adds this as an helptext of this parameter"""
try:
import yaml
except ImportError:
return "---\n"
doc = cls.__doc__
if doc is None:
doc = ""
options = dict()
help: _sdict
specs = dict()
if "argument_spec" in cls._common_args:
specs.update(cls._common_args["argument_spec"])
if "argument_spec" in cls.module_spec:
specs.update(cls.module_spec["argument_spec"])
for option, help in specs.items():
options[option] = dict(
type=help["type"],
)
if hasattr(help, "_help") and help._help is not None:
options[option]["description"] = help._help.split("\n")
if "required" in help and help["required"]:
options[option]["required"] = True
else:
options[option]["required"] = False
if help["type"] == "list":
options[option]["elements"] = help["elements"]
if not options[option]["required"]:
options[option]["default"] = []
if "default" in help:
options[option]["default"] = help["default"]
if "choices" in help:
options[option]["choices"] = tuple(help["choices"])
docu = doc.split("\n")
return str(
yaml.safe_dump(
dict(
module=cls.name,
short_description=docu[0],
description=docu,
options=options,
),
stream=None,
explicit_start="---",
)
)
class SystemdUnitModule(AnsibleModule):
#: path of the unitfile managed by this module
unitfile: pathlib.Path
#: subclasses of this always support the file common args and the check mode
_common_args = dict(
supports_check_mode=True,
add_file_common_args=True,
argument_spec=dict(
description=Types.str(help="An description for programs that access systemd"),
documentation=Types.list(str, help="Paths where documentation can be found"),
requires=Types.list(
str,
help="list of units that this unit requires. If it fails or can't be started this unit fails. without before/after this is started at the same time",
),
wants=Types.list(str, help="list of units that this unit wants. If it fails or can't be started it does not affect this unit"),
partof=Types.list(
str,
help="list of units that this unit is part of.\nIf the restart this unit does it too, but if this restarts it does not affect the other units.",
),
before=Types.list(str, help="list of units that this unit needs to be started before this unit."),
after=Types.list(str, help="list of units that this unit wants to be started after this unit"),
),
)
#: if defined it will be called after run has changed the unitfile
post: Optional[Callable[[], None]]
def unit(self) -> str:
raise NotImplementedError()
def header(self) -> str:
header = "[Unit]\n"
if self.get("description", False):
header += "Description={}\n".format(self.get("description"))
if self.get("documentation", False):
header += "Documentation={}\n".format(" ".join(self.get("documentation")))
if self.get("requires", False):
header += "Requires={}\n".format(" ".join(self.get("requires")))
if self.get("wants", False):
header += "Wants={}\n".format(" ".join(self.get("wants")))
if self.get("partof", False):
header += "PartOf={}\n".format(" ".join(self.get("partof")))
if self.get("before", False):
header += "Before={}\n".format(" ".join(self.get("before")))
if self.get("after", False):
header += "After={}\n".format(" ".join(self.get("after")))
return header
def unitfile_gen(self):
path = self.tmpdir / "newunit"
with open(path, "w") as unit:
unit.write(self.unit())
self.module.set_owner_if_different(path.as_posix(), "root", False)
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()
self.changed = self.changed | self.module.set_owner_if_different(
self.unitfile.as_posix(),
"root",
self.result["changed"],
diff,
)
self.diff(diff)
diff = dict()
self.changed = self.changed | self.module.set_group_if_different(
self.unitfile.as_posix(),
"root",
self.result["changed"],
diff,
)
self.diff(diff)
diff = dict()
self.changed = self.changed | self.module.set_mode_if_different(
self.unitfile.as_posix(),
"0644",
self.result["changed"],
diff,
)
self.diff(diff)
def check(self):
self.unitfile_gen()
if not self.unitfile.exists():
self.diff(before="", after=self.unit(), before_header=self.unitfile.as_posix())
self.changed = True
else:
if self.module.sha256(self.unitfile.as_posix()) != self.module.sha256((self.tmpdir / "newunit").as_posix()):
self.changed = True
self.diff(
before=self.unitfile.read_text(),
after=self.unit(),
before_header=self.unitfile.as_posix(),
)
def run(self):
self.check()
if not self.changed:
return
self.module.atomic_move(
src=(self.tmpdir / "newunit").as_posix(),
dest=self.unitfile.as_posix(),
)
if hasattr(self, "post") and self.post is not None:
self.post()
_INSTALL_MAPPING = dict(
required_by="RequiredBy",
wanted_by="WantedBy",
)
def installable(_class: Type[SystemdUnitModule]):
"""adds the required arguments to the spec and adds the install method for the unit method"""
arguments = dict(
required_by=Types.list(elements=str, help="systemd units that require this mount"),
wanted_by=Types.list(
elements=str,
help="systemd units that want the mount, but not explicitly require it. Commonly used for target if not service explicitly require it.",
),
)
_class.module_spec["argument_spec"].update(arguments)
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):
output += "{}={}\n".format(key, unit)
return output
_class.install = install
return _class

0
plugins/modules/__init__.py Normale Datei
Datei anzeigen

Datei anzeigen

@ -1,41 +1,27 @@
#!/usr/bin/python3 #!/usr/bin/python3
import pathlib import pathlib
from typing import List, Optional from typing import List
try: try:
from ansible_module.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, modspec from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types
from ansible_module.module_utils.module import SystemdUnitModule from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule
except ImportError: except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, modspec from plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdUnitModule from plugins.module_utils.module import SystemdUnitModule
class Module(SystemdUnitModule): # type: ignore class Module(SystemdUnitModule):
"""generates an systemd-networkd link""" """generates an systemd-networkd link"""
name = "link" name = "link"
module_spec = modspec( module_spec = dict(
argument_spec=dict( argument_spec=dict(
mac=Types.str(help="The Mac address of the device." "An ! before the value matches anything but this value."), mac=Types.str(help="The Mac address of the device"),
permanentmac=Types.str( permanentmac=Types.str(help="The Permanent Mac address advertised by the device"),
help="The Permanent Mac address advertised by the device. " "An ! before the value matches anything but this value." path=Types.str(help="A shell-style glob matching the persistent path, as exposed by the udev property ID_PATH."),
), driver=Types.str(help="A glob matching the driver currently bound to the device"),
path=Types.str( type=Types.str(help="A glob matching the device type, as exposed by networkctl list"),
help="A shell-style glob matching the persistent path, as exposed by the udev property ID_PATH. " kind=Types.str(help="a glob matching the device kind, as exposed by networkctl status INTERFACE or ip -d link show INTERFACE."),
"An ! before the value matches anything but this value."
),
driver=Types.str(
help="A glob matching the driver currently bound to the device. " "An ! before the value matches anything but this value."
),
type=Types.str(
help="A glob matching the device type, as exposed by networkctl list. "
"An ! before the value matches anything but this value."
),
kind=Types.str(
help="a glob matching the device kind, as exposed by networkctl status INTERFACE or ip -d link show INTERFACE. "
"An ! before the value matches anything but this value."
),
virtualization=Types.str(help="The virtualization type. An ! before the value matches anything but this value."),
description=Types.str(help="The description for the link"), description=Types.str(help="The description for the link"),
name=Types.str(required=True, help="The new name of the device"), name=Types.str(required=True, help="The new name of the device"),
mtu=Types.int(help="The maximum Transmission unit for the link"), mtu=Types.int(help="The maximum Transmission unit for the link"),
@ -54,24 +40,26 @@ class Module(SystemdUnitModule): # type: ignore
def unit(self) -> str: def unit(self) -> str:
if self.__unit is None: if self.__unit is None:
self.__unit = self._unit(self.match(), self.link()) self.__unit = "\n".join((self.match(), self.link()))
return self.__unit return self.__unit
def match(self) -> Optional[str]: def match(self) -> str:
options = self.map_param( options = []
mac="MACAddress", if self.get("mac", False):
permanentmac="PermanentAddress", options.append("MACAddress={}\n".format(self.get("mac", False)))
path="Path", if self.get("permanentmac", False):
driver="Driver", options.append("PermanentAddress={}\n".format(self.get("permanentmac", False)))
type="Type", if self.get("path", False):
kind="Kind", options.append("Path={}\n".format(self.get("path", False)))
virtualization="Virtualization", if self.get("driver", False):
) options.append("Driver={}\n".format(self.get("driver", False)))
if len(options) == 0: if self.get("type", False):
return None options.append("Type={}\n".format(self.get("type", False)))
if self.get("kind", False):
options.append("Kind={}\n".format(self.get("kind", False)))
return "[Match]\n" + "".join(options) return "[Match]\n" + "".join(options)
def link(self) -> Optional[str]: def link(self) -> str:
options = [] options = []
if self.get("description", False): if self.get("description", False):
options.append("Description={}\n".format(self.get("description", False))) options.append("Description={}\n".format(self.get("description", False)))
@ -79,8 +67,6 @@ class Module(SystemdUnitModule): # type: ignore
options.append("Name={}\n".format(self.get("name", False))) options.append("Name={}\n".format(self.get("name", False)))
if self.get("mtu", False): if self.get("mtu", False):
options.append("MTUBytes={}\n".format(self.get("mtu", False))) options.append("MTUBytes={}\n".format(self.get("mtu", False)))
if len(options) == 0:
return None
return "[Link]\n" + "".join(options) return "[Link]\n" + "".join(options)
def post(self): def post(self):
@ -134,20 +120,18 @@ options:
type: list type: list
driver: driver:
description: description:
- A glob matching the driver currently bound to the device. An ! before the value - A glob matching the driver currently bound to the device
matches anything but this value.
required: false required: false
type: str type: str
kind: kind:
description: description:
- a glob matching the device kind, as exposed by networkctl status INTERFACE or - a glob matching the device kind, as exposed by networkctl status INTERFACE or
ip -d link show INTERFACE. An ! before the value matches anything but this value. ip -d link show INTERFACE.
required: false required: false
type: str type: str
mac: mac:
description: description:
- The Mac address of the device.An ! before the value matches anything but this - The Mac address of the device
value.
required: false required: false
type: str type: str
mtu: mtu:
@ -172,13 +156,12 @@ options:
path: path:
description: description:
- A shell-style glob matching the persistent path, as exposed by the udev property - A shell-style glob matching the persistent path, as exposed by the udev property
ID_PATH. An ! before the value matches anything but this value. ID_PATH.
required: false required: false
type: str type: str
permanentmac: permanentmac:
description: description:
- The Permanent Mac address advertised by the device. An ! before the value matches - The Permanent Mac address advertised by the device
anything but this value.
required: false required: false
type: str type: str
requires: requires:
@ -191,13 +174,7 @@ options:
type: list type: list
type: type:
description: description:
- A glob matching the device type, as exposed by networkctl list. An ! before - A glob matching the device type, as exposed by networkctl list
the value matches anything but this value.
required: false
type: str
virtualization:
description:
- The virtualization type. An ! before the value matches anything but this value.
required: false required: false
type: str type: str
wants: wants:

Datei anzeigen

@ -3,11 +3,11 @@ import pathlib
from typing import List, Optional from typing import List, Optional
try: try:
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule, installable
except ImportError: except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec from plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable from plugins.module_utils.module import SystemdUnitModule, installable
SYSTEMD_SERVICE_CONFIG = pathlib.Path("/etc/systemd/system") SYSTEMD_SERVICE_CONFIG = pathlib.Path("/etc/systemd/system")
@ -18,11 +18,11 @@ OPTION_MAPPING = dict(
@installable @installable
class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc] class Module(SystemdUnitModule):
"""Creates an systemd mount""" """Creates an systemd mount"""
name = "mount" name = "mount"
module_spec = modspec( module_spec = dict(
argument_spec=dict( argument_spec=dict(
fs=Types.str(required=True, help="The filesystem that is used for the mount"), fs=Types.str(required=True, help="The filesystem that is used for the mount"),
where=Types.path(required=True, help="The Path where the filesystem is mounted to"), where=Types.path(required=True, help="The Path where the filesystem is mounted to"),
@ -41,29 +41,39 @@ class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc]
self.unitfile = SYSTEMD_SERVICE_CONFIG.joinpath(self.mountdir.relative_to("/").as_posix().replace("/", "-")).with_suffix(".mount") self.unitfile = SYSTEMD_SERVICE_CONFIG.joinpath(self.mountdir.relative_to("/").as_posix().replace("/", "-")).with_suffix(".mount")
self.__unit = None self.__unit = None
if self.get("description", False) is False: if self.get("description", False) is False:
self.params["description"] = "Mount for {}".format(self.mountdir.as_posix()) self.params["description"] = "Mount for {}".format(self.mountdir.relative_to("/").as_posix())
def unit(self) -> str: def unit(self) -> str:
if self.__unit is None: if self.__unit is None:
self.__unit = self._unit( self.__unit = "\n".join(
self.header(), (
self.mount(), self.header(),
self.install(), # type: ignore[misc,call-arg] self.mount(),
self.install(),
)
) )
return self.__unit return self.__unit
def header(self) -> str: def header(self) -> str:
description = self.get("description", "Mount for {}".format(self.get("where"))) return "[Unit]\nDescription={}\n".format(self.get("description", "Mount for {}".format(self.get("where"))))
return f"[Unit]\nDescription={description}\n"
def mount(self) -> str: def mount(self) -> str:
options = [] output = "[Mount]\n"
options.append("Where={}\n".format(self.get("where"))) output += "Where={}\n".format(self.get("where"))
options.append("What={}\n".format(self.get("what"))) output += "What={}\n".format(self.get("what"))
options.append("Type={}\n".format(self.get("fs"))) output += "Type={}\n".format(self.get("fs"))
if self.get("options", False): if self.get("options", False):
options.append("Options={}\n".format(",".join(self.get("options")))) output += "Options={}\n".format(",".join(self.get("options")))
return "[Mount]\n" + "".join(options) return output
def post(self):
if not self.changed:
return
systemctl = self.module.get_bin_path("systemctl", required=True)
self.module.run_command([systemctl, "daemon-reload"], check_rc=True)
(rc, _, _) = self.module.run_command([systemctl, "is-enabled", self.unitfile.name], check_rc=False)
if rc == 0:
self.module.run_command([systemctl, "restart", self.unitfile.name], check_rc=True)
DOCUMENTATION = """--- DOCUMENTATION = """---

Datei anzeigen

@ -1,210 +0,0 @@
#!/usr/bin/python3
import pathlib
from typing import List, Optional
try:
from ansible_module.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, joindict, modspec, systemdbool
from ansible_module.module_utils.module import SystemdUnitModule
except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import (
SYSTEMD_NETWORK_CONFIG,
Types,
joindict,
modspec,
systemdbool,
)
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdUnitModule
kinds = (
"bond",
"bridge",
"dummy",
"wireguard",
"vlan",
)
matchspec = dict(
host=Types.str(help="hostname of the host that is matched against"),
virtualization=Types.str("Virtualization that is checked against"),
kernelcmd=Types.str(help="checks an kernel commandline argument"),
)
netdevspec = dict(
description=Types.str(help="Description of the device"),
name=Types.str(required=True, help="name of the device"),
kind=Types.str(required=True, help="type of the device", choices=kinds),
)
bridgespec = dict(
stp=Types.bool(help="enable the stp protocol"),
priority=Types.int(help="Priority of the bridge"),
)
bondspec = dict(
mode=Types.str(
help="bonding policy",
choices=("balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb"),
),
minlinks=Types.int(help="Specifies the minimum number of links that must be active before asserting carrier."),
)
wireguardspec = dict(
privatekey=Types.str(help="private key of this side of the tunnel"),
privatekeyfile=Types.path(help="Path of the private key on the host."),
port=Types.raw(help="Port that wireguard uses to listen for packets. the value 'auto' means that the port is automatically decided."),
)
class Module(SystemdUnitModule):
"""Creates an netdev unit that creates an virtual devices"""
name = "netdev"
module_spec = modspec(argument_spec=joindict(bondspec, bridgespec, matchspec, netdevspec))
_common_args = dict(
supports_check_mode=True,
add_file_common_args=True,
)
def prepare(self):
self.__unit = None
self.unitfile = SYSTEMD_NETWORK_CONFIG.joinpath(self.get("name")).with_suffix(".netdev")
def unit(self):
if self.__unit is None:
kind = self.get("kind")
parts = [self.match(), self.netdev()]
if kind != "dummy":
parts.append(getattr(self, kind)())
self.__unit = self._unit(*parts)
return self.__unit
def match(self) -> Optional[str]:
options = self.map_param(
host="Host",
kernelcmd="KernelCommandLine",
virtualization="Virtualization",
)
if len(options) == 0:
return None
return "[Match]\n" + "".join(options)
def netdev(self) -> Optional[str]:
options = self.map_param(
description="Description",
name="Name",
kind="Kind",
)
if len(options) == 0:
return None
return "[NetDev]\n" + "".join(options)
def bond(self) -> Optional[str]:
options = self.map_param(
mode="Mode",
minlinks="MinLinks",
)
if len(options) == 0:
return None
return "[Bond]\n" + "".join(options)
def bridge(self) -> Optional[str]:
options = self.map_param(
stp="STP",
priority="Priority",
)
if len(options) == 0:
return None
return "[Bridge]\n" + "".join(options)
def wireguard(self) -> Optional[str]:
options = self.map_param(
privatekey="PrivateKey",
privatekeyfile="PrivateKeyFile",
port="ListenPort",
)
port = self.get("port", False)
if port not in (False, "auto") and (port < 1 or port > 65535):
raise ValueError("Port must be between 0 and 65536")
if len(options) == 0:
return None
return "[Wireguard]\n" + "".join(options)
def vlan(self) -> Optional[str]:
options: list[str] = []
if len(options) == 0:
return None
return "[Vlan]\n" + "".join(options)
DOCUMENTATION = """---
description:
- Creates an netdev unit that creates an virtual devices
module: netdev
options:
description:
description:
- Description of the device
required: false
type: str
host:
description:
- hostname of the host that is matched against
required: false
type: str
kernelcmd:
description:
- checks an kernel commandline argument
required: false
type: str
kind:
choices:
- bond
- bridge
- dummy
- wireguard
- vlan
description:
- type of the device
required: true
type: str
minlinks:
description:
- Specifies the minimum number of links that must be active before asserting carrier.
required: false
type: int
mode:
choices:
- balance-rr
- active-backup
- balance-xor
- broadcast
- 802.3ad
- balance-tlb
- balance-alb
description:
- bonding policy
required: false
type: str
name:
description:
- name of the device
required: true
type: str
priority:
description:
- Priority of the bridge
required: false
type: int
stp:
description:
- enable the stp protocol
required: false
type: bool
virtualization:
required: true
type: str
short_description: Creates an netdev unit that creates an virtual devices
"""
if __name__ == "__main__":
Module()()

Datei anzeigen

@ -1,28 +1,35 @@
#!/usr/bin/python3 #!/usr/bin/python3
import pathlib import pathlib
from typing import List, Optional, Union from typing import List, Union
try: try:
from ansible_module.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, modspec, systemdbool from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types
from ansible_module.module_utils.module import SystemdUnitModule from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule
except ImportError: except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, modspec, systemdbool from plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdUnitModule from plugins.module_utils.module import SystemdUnitModule
class Module(SystemdUnitModule): # type: ignore def boolconvert(b: Union[bool, str]) -> str:
if b is True:
return "yes"
elif b is False:
return "no"
return b
class Module(SystemdUnitModule):
"""Sets up the systemd network unit""" """Sets up the systemd network unit"""
name = "network" name = "network"
module_spec = modspec( module_spec = dict(
argument_spec=dict( argument_spec=dict(
mac=Types.str(help="The MAC-Address of the device. An ! before the value matches anything but this value."), mac=Types.str(help="The MAC-Address of the device"),
device=Types.str(help="The name of the network device. An ! before the value matches anything but this value."), device=Types.str(help="The name of the network device"),
virtualization=Types.str(help="The virtualization type. An ! before the value matches anything but this value."),
name=Types.str(required=True, help="name of the unit"), name=Types.str(required=True, help="name of the unit"),
dot=Types.bool(help="if DNS-over-TLS should be required or disabled. If it is unset, it will used if the server supports it"), dot=Types.bool(help="if DNS-over-TLS should be required or disabled. If it is unset, it will used if the server supports it"),
dnssec=Types.bool( dnssec=Types.bool(
help="if the Domainqueries should require DNSSEC or not.\nIf its missing, domains that have DNSSEC enabled will be validated, all others it will be assumed to be okay." "if the Domainqueries should require DNSSEC or not. If its missing, domains that have DNSSEC enabled will be validated, all others it will be assumed to be okay."
), ),
dns=Types.list(elements=str, help="List of DNS-Servers"), dns=Types.list(elements=str, help="List of DNS-Servers"),
domain=Types.list(elements=str, help="List of domains that are on this device"), domain=Types.list(elements=str, help="List of domains that are on this device"),
@ -34,13 +41,9 @@ class Module(SystemdUnitModule): # type: ignore
elements=str, elements=str,
help="Routes of networks that can be reached with this device", help="Routes of networks that can be reached with this device",
), ),
masquerade=Types.str(
help="how the packets are modified to look like the come from the computer itself.",
choices=("true", "false", "both", "ipv4", "ipv6", "no"),
),
), ),
required_if=(("defaultdns", True, ("dns",), False),), required_if=(("defaultdns", True, ("dns",), False),),
required_one_of=(("mac", "device", "virtualization"),), required_one_of=(("mac", "device"),),
) )
def prepare(self): def prepare(self):
@ -49,46 +52,47 @@ class Module(SystemdUnitModule): # type: ignore
def unit(self) -> str: def unit(self) -> str:
if self.__unit is None: if self.__unit is None:
self.__unit = self._unit( self.__unit = "\n".join(
self.match(), (
self.network(), self.match(),
self.addresses(), self.network(),
self.routes(), self.addresses(),
self.routes(),
)
) )
return self.__unit return self.__unit
def match(self) -> Optional[str]: def match(self) -> str:
matches = self.map_param( matches = []
mac="MACAddress", if self.get("mac", False):
device="Name", matches.append("MACAddress={}\n".format(self.get("mac")))
virtualization="Virtualization", if self.get("device", False):
) matches.append("Name={}\n".format(self.get("device")))
if len(matches) == 0:
return None
return "[Match]\n" + "".join(matches) return "[Match]\n" + "".join(matches)
def network(self) -> Optional[str]: def network(self) -> str:
output = "[Network]\n"
options = [] options = []
if self.get("description", None) is None: try:
options.append("Description={}".format(self.get("description"))) options.append("Description={}".format(self.get("description")))
server: str except KeyError:
for server in self.get("dns", []): pass
options.append(f"DNS={server}") try:
options.append("DNSDefaultRoute={}".format(self.get("defaultdns", False))) for server in self.get("dns", []):
if self.get("domain", False): options.append(f"DNS={server}")
options.append("Domains={}".format(" ".join(self.get("domain")))) options.append("DNSDefaultRoute={}".format(self.get("defaultdns", False)))
options.append("DNSOverTLS={}".format(systemdbool(self.get("dot", "opportunistic")))) except KeyError:
options.append("DNSSEC={}".format(systemdbool(self.get("dnssec", "allow-downgrade")))) pass
if self.get("masquerade", None) is not None: try:
masquerade: str = self.get("masquerade") domain = self.get("domain")
if masquerade == "true": self.set("domainlog", str(domain))
masquerade = "both" options.append("Domains={}".format(" ".join(domain)))
elif masquerade == "false": options.append("DNSOverTLS={}".format(boolconvert(self.get("dot", "opportunistic"))))
masquerade = "no" options.append("DNSSEC={}".format(boolconvert(self.get("dnssec", "allow-downgrade"))))
options.append(f"IPMasquerade={masquerade}") except KeyError:
if len(options) == 0: pass
return None output += "\n".join(options)
return "[Network]\n" + "".join(options) return output
def addresses(self) -> str: def addresses(self) -> str:
output = [] output = []
@ -96,13 +100,13 @@ class Module(SystemdUnitModule): # type: ignore
output.append(f"[Address]\nAddress={address}\n") output.append(f"[Address]\nAddress={address}\n")
return "\n".join(output) return "\n".join(output)
def routes(self) -> Optional[str]: def routes(self) -> str:
output = [] output = []
routes: list[str] = self.get("route", []) routes = self.get("route", [])
self.set("routes", routes)
for gw in routes: for gw in routes:
output.append(f"[Route]\nGateway={gw}\nGatewayOnLink=yes\nQuickAck=yes\n") output.append(f"[Route]\nGateway={gw}\nGatewayOnLink=yes\nQuickAck=yes\n")
if len(output) == 0: self.set("routes", output)
return None
return "\n".join(output) return "\n".join(output)
@ -144,8 +148,7 @@ options:
type: str type: str
device: device:
description: description:
- The name of the network device. An ! before the value matches anything but this - The name of the network device
value.
required: false required: false
type: str type: str
dns: dns:
@ -156,11 +159,7 @@ options:
required: false required: false
type: list type: list
dnssec: dnssec:
description: required: true
- if the Domainqueries should require DNSSEC or not.
- If its missing, domains that have DNSSEC enabled will be validated, all others
it will be assumed to be okay.
required: false
type: bool type: bool
documentation: documentation:
default: [] default: []
@ -184,20 +183,7 @@ options:
type: bool type: bool
mac: mac:
description: description:
- The MAC-Address of the device. An ! before the value matches anything but this - The MAC-Address of the device
value.
required: false
type: str
masquerade:
choices:
- 'true'
- 'false'
- both
- ipv4
- ipv6
- 'no'
description:
- how the packets are modified to look like the come from the computer itself.
required: false required: false
type: str type: str
name: name:
@ -229,11 +215,6 @@ options:
elements: str elements: str
required: false required: false
type: list type: list
virtualization:
description:
- The virtualization type. An ! before the value matches anything but this value.
required: false
type: str
wants: wants:
default: [] default: []
description: description:

Datei anzeigen

@ -1,197 +0,0 @@
#!/usr/bin/python3
import pathlib
from typing import List, Optional, Union
try:
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
@installable
class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore
"""Creates socket units."""
name = "socket"
module_spec = modspec(
argument_spec=dict(
name=Types.str(required=True, help="Name of the socket"),
stream=Types.list(
Types.str(
help="Name of the stream socket. The name can be a path, an portnumber or an ip with an port. addresses in square brackets are always ipv6 addresses"
)
),
datagram=Types.list(
Types.str(
help="Name of the datagram socket. The name can be a path, an portnumber or an ip with an port. addresses in square brackets are always ipv6 addresses"
)
),
sequential=Types.list(
Types.str(
help="Name of the sequential socket. The name can be a path, an portnumber or an ip with an port. addresses in square brackets are always ipv6 addresses"
)
),
fifo=Types.list(Types.path(help="Name of the fifo. The name must be an absolute path")),
socketuser=Types.str(help="User that owns the socket/fifo"),
socketgroup=Types.str(help="Group that owns the socket/fifo"),
socketmode=Types.str(help="mode of the socket in octal notation", default="0666"),
service=Types.list(str, help="Name of the services that use this socket"),
),
required_one_of=(("stream", "datagram", "sequential", "fifo"),),
)
restartable = False
def prepare(self):
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".socket")
self.__unit = None
def socket(self) -> Optional[str]:
section = self.map_param(
stream="ListenStream",
datagram="ListenDatagram",
sequential="ListenSequential",
fifo="ListenFIFO",
socketuser="SocketUser",
socketgroup="SocketGroup",
socketmode="SocketMode",
)
if len(section) == 0:
return None
return "[Socket]\n" + "".join(section)
def unit(self) -> str:
if self.__unit is None:
self.__unit = self._unit(
self.header(),
self.socket(),
self.install(), # type: ignore[call-arg,misc]
)
return self.__unit
DOCUMENTATION = """---
description:
- Creates socket units.
module: socket
options:
after:
default: []
description:
- list of units that this unit wants to be started after this unit
elements: str
required: false
type: list
before:
default: []
description:
- list of units that this unit needs to be started before this unit.
elements: str
required: false
type: list
datagram:
default: []
elements: str
required: false
type: list
description:
description:
- An description for programs that access systemd
required: false
type: str
documentation:
default: []
description:
- Paths where documentation can be found
elements: str
required: false
type: list
fifo:
default: []
elements: path
required: false
type: list
name:
description:
- Name of the socket
required: true
type: str
partof:
default: []
description:
- list of units that this unit is part of.
- If the restart this unit does it too, but if this restarts it does not affect
the other units.
elements: str
required: false
type: list
required_by:
default: []
description:
- systemd units that require this mount
elements: str
required: false
type: list
requires:
default: []
description:
- list of units that this unit requires. If it fails or can't be started this
unit fails. without before/after this is started at the same time
elements: str
required: false
type: list
sequential:
default: []
elements: str
required: false
type: list
service:
default: []
description:
- Name of the services that use this socket
elements: str
required: false
type: list
socketgroup:
description:
- Group that owns the socket/fifo
required: false
type: str
socketmode:
default: '0666'
description:
- mode of the socket in octal notation
required: false
type: str
socketuser:
description:
- User that owns the socket/fifo
required: false
type: str
stream:
default: []
elements: str
required: false
type: list
wanted_by:
default: []
description:
- systemd units that want the mount, but not explicitly require it. Commonly used
for target if not service explicitly require it.
elements: str
required: false
type: list
wants:
default: []
description:
- list of units that this unit wants. If it fails or can't be started it does
not affect this unit
elements: str
required: false
type: list
short_description: Creates socket units.
"""
if __name__ == "__main__":
Module()()

Datei anzeigen

@ -3,23 +3,23 @@ import pathlib
from typing import List, Union from typing import List, Union
try: try:
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule
except ImportError: except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types from plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable from plugins.module_utils.module import SystemdUnitModule, installable
@installable @installable
class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc] class Module(SystemdUnitModule):
"""Creates System Services units""" """Creates System Services units"""
name = "system_service" name = "system_service"
module_spec = dict( module_spec = dict(
argument_spec=dict( argument_spec=dict(
name=Types.str(required=True, help="Name of the service"), name=Types.str(required=True),
serviceuser=Types.str(help="Username of under which the commands run at.", default="root"), user=Types.str(),
servicegroup=Types.str(help="Group of under which the commands run at.", default="root"), group=Types.str(),
type=Types.str( type=Types.str(
choices=("simple", "exec", "forking", "oneshot", "dbus", "notify", "notify-reload", "idle"), choices=("simple", "exec", "forking", "oneshot", "dbus", "notify", "notify-reload", "idle"),
default="simple", default="simple",
@ -31,116 +31,48 @@ class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc]
"notify and notify-reload notify systemd about the start up via sd_notify. notify-reload needs also inform systemd on reloads and when it is ready again after an reload.\n" "notify and notify-reload notify systemd about the start up via sd_notify. notify-reload needs also inform systemd on reloads and when it is ready again after an reload.\n"
"idle is similar to simple, but it can delay the start up by a few seconds.", "idle is similar to simple, but it can delay the start up by a few seconds.",
), ),
pre=Types.list(elements=str, help="command or list of commands that are started before the main command"), pre=Types.list(str, help="command or list of commands that are started before the main command(Types.str)"),
start=Types.list( start=Types.list(
elements=str, str,
required=True, True,
help="command or list of commands that are started as main programm. Multiple commands are only allowed in a oneshot command", help="command or list of commands that are started as main programm. Multiple commands are only allowed in a oneshot command",
), ),
stop=Types.str(help="command that is started to stop the main program."),
remain=Types.bool(help="should the service remain as started after the command exited"),
post=Types.list(str, help="Command or list of commands that are started after the main command(s) stopped without problems."), post=Types.list(str, help="Command or list of commands that are started after the main command(s) stopped without problems."),
environmentfile=Types.list(
elements=str,
help="List of file that are containing environment variables. They are evaluated before each pre/start/post command",
),
environment=Types.list(
elements=Types.dict(
name=Types.str(help="name of the Environment variable", required=True),
value=Types.str(help="value of the Environment variable", required=True),
),
help="List of environment variables that are set to each command before they run",
),
workingdirectory=Types.str(
help="The Directory that is used for the processes as current working directory",
),
rwpath=Types.list(
elements=Types.path(),
help="Path(s) that are readable and writable (if permission allow)",
),
ropath=Types.list(
elements=Types.path(),
help="Path(s) that are read only",
),
notreadablepath=Types.list(
elements=Types.path(),
help="Path(s) that are not accessible by the applications",
),
execpath=Types.list(
elements=Types.path(),
help="Path(s) where executable files are",
),
noexecpath=Types.list(
elements=Types.path(),
help="Path(s) which are never executable (uploaded files, user accessible paths)",
),
protecthome=Types.str(
help="if true makes user specific directories (/home, /root, /run/user) inaccessible. read-only makes them read only and tmpfs is useful to create binds in it",
choices=("true", "false", "read-only", "tmpfs"),
),
protectsystem=Types.str(
help="makes the system read only. if true /usr, /boot and /efi are read only, if full additionally /etc and if strict all except /proc, /sys and /dev",
choices=("true", "false", "full", "strict"),
),
nonewprivileges=Types.bool(
help="disables the ability to get new capabilities for processes than already granted ones",
),
statedirectory=Types.str(
help="creates an unit specific state directory in /var/lib and sets the env var STATE_DIRECTORY with the path to it. Its cleaned up after the unit is stopped"
),
runtimedirectory=Types.str(
help="creates an unit specific runtime directory in /run and sets the env var RUNTIME_DIRECTORY with the path to it. Its cleaned up after the unit is stopped"
),
restart=Types.str(),
restartsec=Types.str(),
), ),
) )
def prepare(self): def prepare(self):
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".service") self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_stem(".service")
self.__unit = None
if self.get("type", "simple") != "oneshot" and len(self.get("start")) > 1: if self.get("type", "simple") != "oneshot" and len(self.get("start")) > 1:
self.fail("only oneshot services are allowed to have multiple start commands") self.module.fail_json("only oneshot services are allowed to have multiple start commands", **self.result)
def service(self) -> str: def service(self):
params = [] section = "[Service]\n"
if self.get("environment", False): if self.get("type"):
for env in self.get("environment"): section += "Type=".format(self.get("type"))
params.append(f"Environment={env['name']}={env['value']}\n") if self.get("pre", False):
params.extend( for cmd in self.get("pre"):
self.map_param( section += "ExecStartPre={}".format(cmd)
type="Type", if self.get("start", False):
pre="ExecStartPre", for cmd in self.get("start"):
start="ExecStart", section += "ExecStart={}".format(cmd)
stop="ExecStop", if self.get("post", False):
post="ExecStartPost", for cmd in self.get("post"):
serviceuser="User", section += "ExecStartPost={}".format(cmd)
servicegroup="Group", if self.get("user", False):
workingdirectory="WorkingDirectory", section += "User={}".format(self.get("user"))
environmentfile="EnvironmentFile", if self.get("group", False):
protecthome="ProtectHome", section += "Group={}".format(self.get("group"))
protectsystem="ProtectSystem", return section
rwpath="ReadWritePaths",
ropath="ReadOnlyPaths",
notreadablepath="InaccessiblePaths",
execpath="ExecPaths",
noexecpath="NoExecPaths",
statedirectory="StateDirectory",
runtimedirectory="RuntimeDirectory",
nonewprivileges="NoNewPriviledges",
remain="RemainAfterExit",
restart="Restart",
restartsec="RestartSec",
)
)
return "[Service]\n" + "".join(params)
def unit(self) -> str: def unit(self) -> str:
if self.__unit is None: if self.__unit is None:
self.__unit = self._unit( self.__unit = "\n".join(
self.header(), (
self.service(), self.header(),
self.install(), # type: ignore[call-arg,misc] self.service(),
self.install(),
)
) )
return self.__unit return self.__unit
@ -176,64 +108,12 @@ options:
elements: str elements: str
required: false required: false
type: list type: list
environment: group:
default: []
description:
- List of environment variables that are set to each command before they run
elements: dict
options:
name:
description:
- name of the Environment variable
required: true
type: str
value:
description:
- value of the Environment variable
required: true
type: str
required: false required: false
type: list type: str
environmentfile:
default: []
description:
- List of file that are containing environment variables. They are evaluated before
each pre/start/post command
elements: str
required: false
type: list
execpath:
default: []
description:
- Path(s) where executable files are
elements: path
required: false
type: list
name: name:
description:
- Name of the service
required: true required: true
type: str type: str
noexecpath:
default: []
description:
- Path(s) which are never executable (uploaded files, user accessible paths)
elements: path
required: false
type: list
nonewprivileges:
description:
- disables the ability to get new capabilities for processes than already granted
ones
required: false
type: bool
notreadablepath:
default: []
description:
- Path(s) that are not accessible by the applications
elements: path
required: false
type: list
partof: partof:
default: [] default: []
description: description:
@ -254,37 +134,10 @@ options:
pre: pre:
default: [] default: []
description: description:
- command or list of commands that are started before the main command - command or list of commands that are started before the main command(Types.str)
elements: str elements: str
required: false required: false
type: list type: list
protecthome:
choices:
- 'true'
- 'false'
- read-only
- tmpfs
description:
- if true makes user specific directories (/home, /root, /run/user) inaccessible.
read-only makes them read only and tmpfs is useful to create binds in it
required: false
type: str
protectsystem:
choices:
- 'true'
- 'false'
- full
- strict
description:
- makes the system read only. if true /usr, /boot and /efi are read only, if full
additionally /etc and if strict all except /proc, /sys and /dev
required: false
type: str
remain:
description:
- should the service remain as started after the command exited
required: false
type: bool
required_by: required_by:
default: [] default: []
description: description:
@ -300,44 +153,6 @@ options:
elements: str elements: str
required: false required: false
type: list type: list
restart:
required: false
type: str
restartsec:
required: false
type: str
ropath:
default: []
description:
- Path(s) that are read only
elements: path
required: false
type: list
runtimedirectory:
description:
- creates an unit specific runtime directory in /run and sets the env var RUNTIME_DIRECTORY
with the path to it. Its cleaned up after the unit is stopped
required: false
type: str
rwpath:
default: []
description:
- Path(s) that are readable and writable (if permission allow)
elements: path
required: false
type: list
servicegroup:
default: root
description:
- Group of under which the commands run at.
required: false
type: str
serviceuser:
default: root
description:
- Username of under which the commands run at.
required: false
type: str
start: start:
description: description:
- command or list of commands that are started as main programm. Multiple commands - command or list of commands that are started as main programm. Multiple commands
@ -345,17 +160,6 @@ options:
elements: str elements: str
required: true required: true
type: list type: list
statedirectory:
description:
- creates an unit specific state directory in /var/lib and sets the env var STATE_DIRECTORY
with the path to it. Its cleaned up after the unit is stopped
required: false
type: str
stop:
description:
- command that is started to stop the main program.
required: false
type: str
type: type:
choices: choices:
- simple - simple
@ -382,6 +186,9 @@ options:
- idle is similar to simple, but it can delay the start up by a few seconds. - idle is similar to simple, but it can delay the start up by a few seconds.
required: false required: false
type: str type: str
user:
required: false
type: str
wanted_by: wanted_by:
default: [] default: []
description: description:
@ -398,11 +205,6 @@ options:
elements: str elements: str
required: false required: false
type: list type: list
workingdirectory:
description:
- The Directory that is used for the processes as current working directory
required: false
type: str
short_description: Creates System Services units short_description: Creates System Services units
""" """

Datei anzeigen

@ -3,48 +3,33 @@ import pathlib
from typing import List, Union from typing import List, Union
try: try:
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, systemdbool from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule
except ImportError: except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, systemdbool from plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable from plugins.module_utils.module import SystemdUnitModule, installable
__module_name__ = "TargetModule"
@installable @installable
class TargetModule(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc] class Module(SystemdUnitModule):
"""Creates Target units""" """Creates Target units"""
name = "target" name = "target"
module_spec = dict( module_spec = dict(
argument_spec=dict( argument_spec=dict(
description=Types.str(help="description of the target"), description=Types.str(required=True),
name=Types.str(required=True, help="name of the target"), name=Types.str(required=True),
allow_isolate=Types.bool( allow_isolate=Types.bool(default=False),
default=False,
help="allows admins to restrict the system to only start units that are wanted by this unit and subsequent units",
),
), ),
) )
restartable = False
def prepare(self):
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".target")
self.__unit = None
def header(self) -> str:
section = super().header()
if section is None:
section = "[Unit]\n"
section += "AllowIsolate={}\n".format(systemdbool(self.get("allow_isolate", False)))
return section
def unit(self) -> str: def unit(self) -> str:
if self.__unit is None: if self.__unit is None:
self.__unit = self._unit( self.__unit = "\n".join(
self.header(), (
self.install(), # type: ignore[call-arg,misc] self.header(),
self.install(),
)
) )
return self.__unit return self.__unit
@ -63,9 +48,6 @@ options:
type: list type: list
allow_isolate: allow_isolate:
default: false default: false
description:
- allows admins to restrict the system to only start units that are wanted by
this unit and subsequent units
required: false required: false
type: bool type: bool
before: before:
@ -76,9 +58,7 @@ options:
required: false required: false
type: list type: list
description: description:
description: required: true
- description of the target
required: false
type: str type: str
documentation: documentation:
default: [] default: []
@ -88,8 +68,6 @@ options:
required: false required: false
type: list type: list
name: name:
description:
- name of the target
required: true required: true
type: str type: str
partof: partof:
@ -136,4 +114,4 @@ short_description: Creates Target units
""" """
if __name__ == "__main__": if __name__ == "__main__":
TargetModule()() Module()()

Datei anzeigen

@ -1,252 +0,0 @@
#!/usr/bin/python3
import pathlib
from typing import List, Union
try:
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
__module_name__ = "TimerModule"
@installable
class TimerModule(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc]
"""Creates Timer units"""
name = "timer"
module_spec = modspec(
argument_spec=dict(
name=Types.str(required=True, help="Name of the unit"),
description=Types.str(help="Description of the timer"),
onactive=Types.list(
help="Starts the service x seconds after this timer was activated",
elements=Types.str(),
),
onboot=Types.list(
help="Starts the service x seconds after the device was booted or the container was started",
elements=Types.str(),
),
onstartup=Types.list(
help="Starts the service x seconds after the System's/User's systemd instance was started",
elements=Types.str(),
),
onunitactive=Types.list(
help="Starts the service x seconds after the unit this timer activates was last activated",
elements=Types.str(),
),
onunitinactive=Types.list(
help="Starts the service x seconds after the unit this timer activates was last deactivated",
elements=Types.str(),
),
oncalendar=Types.list(
help="Uses an Time string to start the unit.",
elements=Types.str(),
),
persistent=Types.bool(
help="If the system was down in the time the timer would have started the unit, start the unit as soon as possible."
),
randomdelay=Types.str(help="delays the activation by an random delay between 0 and the value"),
fixdelay=Types.bool(
help="set the random delay to an fixed value. It uses the timername, the user of the servicemanager and the machineid as the seed."
),
unit=Types.str(help="The name of the unit. only needed if its not {{name}}.service"),
),
required_one_of=[
(
"onactive",
"onboot",
"onstartup",
"onunitactive",
"onunitinactive",
"oncalendar",
),
],
)
def prepare(self):
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".timer")
self.__unit = None
def body(self):
section = "[Timer]\n"
params = []
params.extend(
self.map_param(
onactive="OnActiveSec",
onboot="OnBootSec",
onstartup="OnStartupSec",
onunitactive="OnUnitActiveSec",
onunitinactive="OnUnitInactiveSec",
oncalendar="OnCalendar",
persistent="Persistent",
randomdelay="RandomizedDelaySec",
fixdelay="FixedRandomDelay",
unit="Unit",
),
)
if len(params) == 0:
return None
section += "".join(params)
return section
def unit(self) -> str:
if self.__unit is None:
self.__unit = self._unit(
self.header(),
self.body(),
self.install(), # type: ignore[call-arg,misc]
)
return self.__unit
DOCUMENTATION = """---
description:
- Creates Timer units
module: timer
options:
after:
default: []
description:
- list of units that this unit wants to be started after this unit
elements: str
required: false
type: list
before:
default: []
description:
- list of units that this unit needs to be started before this unit.
elements: str
required: false
type: list
description:
description:
- Description of the timer
required: false
type: str
documentation:
default: []
description:
- Paths where documentation can be found
elements: str
required: false
type: list
fixdelay:
description:
- set the random delay to an fixed value. It uses the timername, the user of the
servicemanager and the machineid as the seed.
required: false
type: bool
name:
description:
- Name of the unit
required: true
type: str
onactive:
default: []
description:
- Starts the service x seconds after this timer was activated
elements: str
required: false
type: list
onboot:
default: []
description:
- Starts the service x seconds after the device was booted or the container was
started
elements: str
required: false
type: list
oncalendar:
default: []
description:
- Uses an Time string to start the unit.
elements: str
required: false
type: list
onstartup:
default: []
description:
- Starts the service x seconds after the System's/User's systemd instance was
started
elements: str
required: false
type: list
onunitactive:
default: []
description:
- Starts the service x seconds after the unit this timer activates was last activated
elements: str
required: false
type: list
onunitinactive:
default: []
description:
- Starts the service x seconds after the unit this timer activates was last deactivated
elements: str
required: false
type: list
partof:
default: []
description:
- list of units that this unit is part of.
- If the restart this unit does it too, but if this restarts it does not affect
the other units.
elements: str
required: false
type: list
persistent:
description:
- If the system was down in the time the timer would have started the unit, start
the unit as soon as possible.
required: false
type: bool
randomdelay:
description:
- delays the activation by an random delay between 0 and the value
required: false
type: str
required_by:
default: []
description:
- systemd units that require this mount
elements: str
required: false
type: list
requires:
default: []
description:
- list of units that this unit requires. If it fails or can't be started this
unit fails. without before/after this is started at the same time
elements: str
required: false
type: list
unit:
description:
- The name of the unit. only needed if its not {{name}}.service
required: false
type: str
wanted_by:
default: []
description:
- systemd units that want the mount, but not explicitly require it. Commonly used
for target if not service explicitly require it.
elements: str
required: false
type: list
wants:
default: []
description:
- list of units that this unit wants. If it fails or can't be started it does
not affect this unit
elements: str
required: false
type: list
short_description: Creates Timer units
"""
if __name__ == "__main__":
TimerModule()()

Datei anzeigen

@ -1,47 +0,0 @@
#!/usr/bin/python3
import pathlib
from typing import List, Union
try:
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
except ImportError:
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types, modspec
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
@installable
class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore
"""Creates units"""
name = "unit"
module_spec = modspec(
argument_spec=dict(
name=Types.str(required=True, help="Name of the unit"),
)
)
def prepare(self):
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".")
self.__unit = None
def body(self):
section = "[]\n"
return section
def unit(self) -> str:
if self.__unit is None:
self.__unit = "\n".join(
(
self.header(),
self.body(),
self.install(),
)
)
return self.__unit
DOCUMENTATION = """"""
if __name__ == "__main__":
Module()()

Datei anzeigen

@ -5,6 +5,3 @@ line-length = 140
atomic = true atomic = true
profile = "black" profile = "black"
line_length = 140 line_length = 140
[tool.mypy]
disable_error_code = ["import-untyped", "no-redef", "attr-defined"]

Datei anzeigen

@ -0,0 +1,39 @@
import os
import unittest
try: # pragma: nocover
from ansible_collections.sebastian.systemd.plugins.module_utils.generic import Types
except ImportError: # pragma: nocover
import sys
sys.path.append("plugins/module_utils")
from generic import Types
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.assertEquals(output["type"], "str")
self.assertEquals(Types.str.__name__, "str")
self.assertEquals(output["required"], True)
self.assertEquals(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.assertEquals(output["type"], "list")
self.assertEquals(output["required"], False)
self.assertEquals(output["elements"], "str")

40
update_doc Ausführbare Datei
Datei anzeigen

@ -0,0 +1,40 @@
#!/usr/bin/python
import importlib
import pathlib
import re
import sys
sys.path.append(".")
mindocstring = "DOCUMENTATION = ''''''"
moduledir = pathlib.Path("plugins/modules")
regex = re.compile("DOCUMENTATION *= *r?(?P<quote>\"{3}|'{3})(---)?.*?(?P=quote)", re.MULTILINE | re.DOTALL)
if __name__ == "__main__":
for modfile in moduledir.iterdir():
if modfile.name in ( "__init__.py", "__pycache__"):
continue
mod = importlib.import_module(".".join((modfile.parts[:-1])+(modfile.stem,)))
if hasattr(mod, "Module"):
module = mod.Module
elif hasattr(mod, "__module_name__"):
module = getattr(mod, mod.__module_name__)
else:
print("Error loading Module of File: {}. No Module or __module_name__ defined".format(modfile))
continue
try:
moddoc = module.doc()
except AttributeError:
print("Broken module. skipping {}".format(modfile))
continue
except Exception as e:
print("Error in documentation of module {}: {}".format(modfile, e))
continue
moddata = modfile.read_text()
match = regex.search(moddata)
if not match:
print("no Documentation set for module {}. Please add at least \"{}\" to the file".format(modfile.stem, mindocstring))
continue
newmod = "{pre}DOCUMENTATION = {quote}{doc}{quote}{post}".format(pre=moddata[:match.start()], quote=match.group("quote"), doc=moddoc, post=moddata[match.end():])
modfile.write_text(newmod)
# code: python

Datei anzeigen

@ -1,6 +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\nfile name: %s\n" $user $package $version "$user-$package-$version.tar.gz"
ansible-galaxy collection publish dist/*