1
0
Fork 0

Commits vergleichen

...

76 Commits

Autor SHA1 Nachricht Datum
Sebastian Tobie 7c24aee1c7 raised the minimal version of the module 2024-03-16 10:44:42 +01:00
Sebastian Tobie 73889fe8ef fixed an type 2024-03-11 21:17:24 +01:00
Sebastian Tobie 31b5fa26a8 uploads are now usign the galaxy 2024-03-11 20:42:20 +01:00
Sebastian Tobie 531d8fdb0e a bit formatting 2024-03-11 20:41:12 +01:00
Sebastian Tobie aa53ad470a new options for system_service 2024-03-11 20:41:02 +01:00
Sebastian Tobie 8ee18934a9 updated the dependency sebastian.base to the 0.4.0 methods 2024-03-09 00:13:24 +01:00
Sebastian Tobie 3d528fe1a6 version bump 2024-03-04 23:07:49 +01:00
Sebastian Tobie 548dacd614 some formatting 2024-03-04 23:07:40 +01:00
Sebastian Tobie 1755385a6b renamed Module 2024-03-04 23:07:27 +01:00
Sebastian Tobie d64aeaa4fa added timer module 2024-02-24 11:33:30 +01:00
Sebastian Tobie 8efa463b3d bumped the version to include the print fix 2024-02-24 11:05:03 +01:00
Sebastian Tobie fe4064b3c3 bumped the minimal version up 2024-02-24 10:19:44 +01:00
Sebastian Tobie f61e735a41 updated changelog 2024-02-11 23:35:27 +01:00
Sebastian Tobie e1b9a7928e removed the __init__.py 2024-02-11 23:35:05 +01:00
Sebastian Tobie 872e1c1883 another fix for the import 2024-02-11 20:59:50 +01:00
Sebastian Tobie f843ffdef3 fixed the import of the module to the correct one 2024-02-11 20:35:06 +01:00
Sebastian Tobie dd8493d059 fixed the missing space 2024-02-11 14:39:52 +01:00
Sebastian Tobie 3198a0d064 fixed the import 2024-02-11 14:35:00 +01:00
Sebastian Tobie 022b5d3ee5 fixed upload.sh 2023-12-30 14:41:03 +01:00
Sebastian Tobie 9e05b08500 fixed some things 2023-12-30 14:23:01 +01:00
Sebastian Tobie 2e74882b7b reformated changelog.yml 2023-12-10 22:51:55 +01:00
Sebastian Tobie 0ae59dc167 added an script to upload the package 2023-12-10 20:51:54 +01:00
Sebastian Tobie c0dfa0a951 moved the package to the ansible namespace 2023-12-10 20:51:41 +01:00
Sebastian Tobie eec5816319 updated makefile 2023-12-09 13:05:36 +01:00
Sebastian Tobie 2671cb7401 better formatting 2023-12-09 13:04:21 +01:00
Sebastian Tobie 855b1c0466 fixed dependency and removed unused script 2023-11-26 16:13:11 +01:00
Sebastian Tobie 283e551204 migrated the module_utils to an indipendent repository 2023-11-26 10:53:45 +01:00
Sebastian Tobie affe76fbcf removed tests 2023-11-24 21:45:50 +01:00
Sebastian Tobie 4242ce0c95 added more keys 2023-11-23 19:08:51 +01:00
Sebastian Tobie 920da46658 added boolean handling 2023-11-23 18:54:34 +01:00
Sebastian Tobie 1c3d89e961 removed an missunderstanding in the docs 2023-07-16 10:20:44 +02:00
Sebastian Tobie e845a06d09 fixed an typing error 2023-07-16 09:30:48 +02:00
Sebastian Tobie 48872edc14 fixed galaxy.yml 2023-07-16 09:26:01 +02:00
Sebastian Tobie 9e2b4eef8b added an bare netdev module to create netdevs 2023-07-16 09:24:45 +02:00
Sebastian Tobie 5feb959f42 added virtualization and negative matches to link and network 2023-07-15 22:25:59 +02:00
Sebastian Tobie 62e4cbe040 fixed the default description 2023-05-01 10:29:21 +02:00
Sebastian Tobie dcc685616b sockets and targets are now not restarted on changes 2023-04-26 19:03:26 +02:00
Sebastian Tobie 70e0146d02 new release 0.3.1 2023-04-26 19:02:06 +02:00
Sebastian Tobie 23d1c655f4 added an minimum ansible version 2023-04-26 19:01:48 +02:00
Sebastian Tobie de7292f3e6 moved the makefile 2023-04-26 19:01:27 +02:00
Sebastian Tobie 3e0cb17222 added the ability for the author of the module to decide if the module is not restartable/needs restarts 2023-04-26 19:01:08 +02:00
Sebastian Tobie 20bb6b1f16 added typehints for the reload mixin 2023-04-26 19:00:16 +02:00
Sebastian Tobie c3404200a9 map_param now adds newlines to the lines it generates 2023-04-26 18:58:30 +02:00
Sebastian Tobie ec82abe3d4 new release 0.3.0 2023-04-23 22:18:25 +02:00
Sebastian Tobie c47418cace fixed name of modspec import 2023-04-23 19:37:49 +02:00
Sebastian Tobie 6e1182048e fixed map_param 2023-04-23 11:26:41 +02:00
Sebastian Tobie 7246f9bfcb added socket module 2023-04-23 10:37:23 +02:00
Sebastian Tobie afa7299fcd updated units to the current state 2023-04-23 10:32:42 +02:00
Sebastian Tobie 7bec7376db fixed the description generation part of docify 2023-04-23 10:25:03 +02:00
Sebastian Tobie 931ed6d4f8 removed the SUnit function from the mixin 2023-04-23 10:21:38 +02:00
Sebastian Tobie a02d261fe3 updated the docify function 2023-04-23 10:21:14 +02:00
Sebastian Tobie c72de48b62 added an AnsibleParameter Type Alias and replaced all the old usage of _sdict with it 2023-04-23 10:03:10 +02:00
Sebastian Tobie 2e0ae0267f updated the update_doc module for the new example unit 2023-04-23 09:36:24 +02:00
Sebastian Tobie ebfe00d1a4 added an example unitfile 2023-04-23 09:34:10 +02:00
Sebastian Tobie e006d94584 added an mixin for reloading and restarting changed services 2023-04-23 09:33:47 +02:00
Sebastian Tobie e94dbec9f4 eased the redability of the installable decorator 2023-04-23 09:30:43 +02:00
Sebastian Tobie 3888436ad4 added unitfile as an result 2023-04-23 09:29:04 +02:00
Sebastian Tobie f7880850dc added map_param to ease the generation simple parameters for systemd.
it reduces the boilerplate for simple key-value parameters
2023-04-23 09:23:15 +02:00
Sebastian Tobie f2f3580337 moved the doc generation to an recursive function 2023-04-23 09:17:41 +02:00
Sebastian Tobie d2375e07a6 fixed the raise of KeyError since all values are None if unset 2023-04-23 09:16:48 +02:00
Sebastian Tobie 1884f4045e moved type definitons completly to overloaded methods 2023-04-23 09:15:37 +02:00
Sebastian Tobie e4f25c2ffa fixed an problem with arguments that got updated in different modules 2023-04-23 09:13:07 +02:00
Sebastian Tobie 652b7e961d added modspec as an helper function.
it has the typedefinition that allows the developer to get the required parameters for ansible modules more easily
2023-04-23 09:08:44 +02:00
Sebastian Tobie a2174af565 changed the complicated _Type to an simple type and Types to an instance 2023-04-23 09:06:46 +02:00
Sebastian Tobie e66ef401a9 fixed name 2023-04-21 21:14:30 +02:00
Sebastian Tobie 9ccdc46248 somemore debugging 2023-04-21 21:11:24 +02:00
Sebastian Tobie b6a77ebd6d added some debugging 2023-04-21 21:09:12 +02:00
Sebastian Tobie 6668d82f40 fixed get 2023-04-21 21:05:03 +02:00
Sebastian Tobie aa1a4db56c added missing __unit cache 2023-04-21 20:56:16 +02:00
Sebastian Tobie d1390f7150 fixed path generation 2023-04-21 20:53:09 +02:00
Sebastian Tobie 68e2560dc1 added the missing prepare 2023-04-21 20:52:00 +02:00
Sebastian Tobie 49c3ef1ae7 allow_isolate is now used 2023-04-21 20:49:21 +02:00
Sebastian Tobie 76f4e64081 generalized the systemdbool function 2023-04-21 20:49:07 +02:00
Sebastian Tobie e2dea89599 forgot an import 2023-04-21 20:40:06 +02:00
Sebastian Tobie a08f6abaa2 forgot to update galaxy.yml 2023-04-21 20:39:05 +02:00
Sebastian Tobie 4210072da1 forgot to update changelog 2023-04-21 18:12:32 +02:00
25 geänderte Dateien mit 1348 neuen und 783 gelöschten Zeilen

Datei anzeigen

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

Datei anzeigen

@ -1,10 +1,114 @@
=====================================
sebastian.systemd 0.1.3 Release Notes
sebastian.systemd 0.4.3 Release Notes
=====================================
.. 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
======

15
Makefile Normale Datei
Datei anzeigen

@ -0,0 +1,15 @@
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,37 +2,75 @@ ancestor: null
releases:
0.1.0:
changes:
release_date: '2023-04-15'
release_summary: Erstes Release
major_changes:
- added systemd_link module
- added systemd_mount module
- added systemd_network module
modules:
- 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
release_summary: Erstes Release
release_date: "2023-04-15"
0.1.1:
changes:
release_date: '2023-04-15'
release_summary: kleines feature update
minor_changes:
- update_doc hinzugefügt. um Dokumentation automatisch zu aktualisieren
- "update_doc hinzugef\xFCgt. um Dokumentation automatisch zu aktualisieren"
release_summary: kleines feature update
release_date: "2023-04-15"
0.1.2:
changes:
release_date: '2023-04-15'
release_summary: Pfad der auflösung für Tests und autodoc geändert, weil Mitogen sonst blockiert
release_summary: "Pfad der aufl\xF6sung f\xFCr Tests und autodoc ge\xE4ndert, weil Mitogen sonst blockiert"
release_date: "2023-04-15"
0.1.3:
changes:
release_date: "2023-04-20"
release_summary: "renamed all modules to names without systemd_ prefix"
release_summary: renamed all modules to names without systemd_ prefix
release_date: "2023-04-20"
0.2.0:
changes:
release_date: "2023-04-21"
release_summary: "added an rudimentary system_service module"
modules:
- name: system_service
description: erstellt ein einfachen systemd system service
release_summary: added an rudimentary system_service module
release_date: "2023-04-21"
0.3.0:
changes:
release_summary: added socket module and some small fixes
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,21 +1,30 @@
---
namespace: sebastian
name: systemd
version: 0.1.2
version: 0.4.3
readme: README.md
authors:
- Sebastian Tobie
description: An simple for generating systemd units with ansible
license_file: 'LICENSE'
license_file: "LICENSE"
tags:
- systemd
- linux
dependencies: {}
repository: https://gitea.sebastian-tobie.de/sebastian/ansible-systemd.git
dependencies:
sebastian.base: ">=0.4.3"
repository: https://gitea.sebastian-tobie.de/ansible/ansible-systemd.git
# documentation: http://docs.example.com
homepage: https://gitea.sebastian-tobie.de/sebastian/ansible-systemd
issues: https://gitea.sebastian-tobie.de/sebastian/ansible-systemd/issues
build_ignore: []
homepage: https://gitea.sebastian-tobie.de/ansible/ansible-systemd
issues: https://gitea.sebastian-tobie.de/ansible/ansible-systemd/issues
build_ignore:
- "*.gz"
- ".*"
- Makefile
- pyproject.toml
- upload.sh
- htmlcov
- changelogs
- docs
# manifest: null

Datei anzeigen

@ -1,52 +1,4 @@
---
# Collections must specify a minimum required ansible version to upload
# to galaxy
# 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
requires_ansible: '>=2.9.10'

Datei anzeigen

Datei anzeigen

@ -1,128 +0,0 @@
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

Datei anzeigen

@ -1,315 +0,0 @@
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

Datei anzeigen

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

Datei anzeigen

@ -3,11 +3,11 @@ import pathlib
from typing import List, Optional
try:
from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule, installable
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 plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from plugins.module_utils.module import SystemdUnitModule, installable
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
SYSTEMD_SERVICE_CONFIG = pathlib.Path("/etc/systemd/system")
@ -18,11 +18,11 @@ OPTION_MAPPING = dict(
@installable
class Module(SystemdUnitModule):
class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc]
"""Creates an systemd mount"""
name = "mount"
module_spec = dict(
module_spec = modspec(
argument_spec=dict(
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"),
@ -41,39 +41,29 @@ class Module(SystemdUnitModule):
self.unitfile = SYSTEMD_SERVICE_CONFIG.joinpath(self.mountdir.relative_to("/").as_posix().replace("/", "-")).with_suffix(".mount")
self.__unit = None
if self.get("description", False) is False:
self.params["description"] = "Mount for {}".format(self.mountdir.relative_to("/").as_posix())
self.params["description"] = "Mount for {}".format(self.mountdir.as_posix())
def unit(self) -> str:
if self.__unit is None:
self.__unit = "\n".join(
(
self.header(),
self.mount(),
self.install(),
)
self.__unit = self._unit(
self.header(),
self.mount(),
self.install(), # type: ignore[misc,call-arg]
)
return self.__unit
def header(self) -> str:
return "[Unit]\nDescription={}\n".format(self.get("description", "Mount for {}".format(self.get("where"))))
description = self.get("description", "Mount for {}".format(self.get("where")))
return f"[Unit]\nDescription={description}\n"
def mount(self) -> str:
output = "[Mount]\n"
output += "Where={}\n".format(self.get("where"))
output += "What={}\n".format(self.get("what"))
output += "Type={}\n".format(self.get("fs"))
options = []
options.append("Where={}\n".format(self.get("where")))
options.append("What={}\n".format(self.get("what")))
options.append("Type={}\n".format(self.get("fs")))
if self.get("options", False):
output += "Options={}\n".format(",".join(self.get("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)
options.append("Options={}\n".format(",".join(self.get("options"))))
return "[Mount]\n" + "".join(options)
DOCUMENTATION = """---

210
plugins/modules/netdev.py Normale Datei
Datei anzeigen

@ -0,0 +1,210 @@
#!/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,35 +1,28 @@
#!/usr/bin/python3
import pathlib
from typing import List, Union
from typing import List, Optional, Union
try:
from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types
from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule
from ansible_module.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, modspec, systemdbool
from ansible_module.module_utils.module import SystemdUnitModule
except ImportError:
from plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types
from plugins.module_utils.module import SystemdUnitModule
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_NETWORK_CONFIG, Types, modspec, systemdbool
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdUnitModule
def boolconvert(b: Union[bool, str]) -> str:
if b is True:
return "yes"
elif b is False:
return "no"
return b
class Module(SystemdUnitModule):
class Module(SystemdUnitModule): # type: ignore
"""Sets up the systemd network unit"""
name = "network"
module_spec = dict(
module_spec = modspec(
argument_spec=dict(
mac=Types.str(help="The MAC-Address of the device"),
device=Types.str(help="The name of the network device"),
mac=Types.str(help="The MAC-Address of the device. An ! before the value matches anything but this value."),
device=Types.str(help="The name of the network device. 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."),
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"),
dnssec=Types.bool(
"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."
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."
),
dns=Types.list(elements=str, help="List of DNS-Servers"),
domain=Types.list(elements=str, help="List of domains that are on this device"),
@ -41,9 +34,13 @@ class Module(SystemdUnitModule):
elements=str,
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_one_of=(("mac", "device"),),
required_one_of=(("mac", "device", "virtualization"),),
)
def prepare(self):
@ -52,47 +49,46 @@ class Module(SystemdUnitModule):
def unit(self) -> str:
if self.__unit is None:
self.__unit = "\n".join(
(
self.match(),
self.network(),
self.addresses(),
self.routes(),
)
self.__unit = self._unit(
self.match(),
self.network(),
self.addresses(),
self.routes(),
)
return self.__unit
def match(self) -> str:
matches = []
if self.get("mac", False):
matches.append("MACAddress={}\n".format(self.get("mac")))
if self.get("device", False):
matches.append("Name={}\n".format(self.get("device")))
def match(self) -> Optional[str]:
matches = self.map_param(
mac="MACAddress",
device="Name",
virtualization="Virtualization",
)
if len(matches) == 0:
return None
return "[Match]\n" + "".join(matches)
def network(self) -> str:
output = "[Network]\n"
def network(self) -> Optional[str]:
options = []
try:
if self.get("description", None) is None:
options.append("Description={}".format(self.get("description")))
except KeyError:
pass
try:
for server in self.get("dns", []):
options.append(f"DNS={server}")
options.append("DNSDefaultRoute={}".format(self.get("defaultdns", False)))
except KeyError:
pass
try:
domain = self.get("domain")
self.set("domainlog", str(domain))
options.append("Domains={}".format(" ".join(domain)))
options.append("DNSOverTLS={}".format(boolconvert(self.get("dot", "opportunistic"))))
options.append("DNSSEC={}".format(boolconvert(self.get("dnssec", "allow-downgrade"))))
except KeyError:
pass
output += "\n".join(options)
return output
server: str
for server in self.get("dns", []):
options.append(f"DNS={server}")
options.append("DNSDefaultRoute={}".format(self.get("defaultdns", False)))
if self.get("domain", False):
options.append("Domains={}".format(" ".join(self.get("domain"))))
options.append("DNSOverTLS={}".format(systemdbool(self.get("dot", "opportunistic"))))
options.append("DNSSEC={}".format(systemdbool(self.get("dnssec", "allow-downgrade"))))
if self.get("masquerade", None) is not None:
masquerade: str = self.get("masquerade")
if masquerade == "true":
masquerade = "both"
elif masquerade == "false":
masquerade = "no"
options.append(f"IPMasquerade={masquerade}")
if len(options) == 0:
return None
return "[Network]\n" + "".join(options)
def addresses(self) -> str:
output = []
@ -100,13 +96,13 @@ class Module(SystemdUnitModule):
output.append(f"[Address]\nAddress={address}\n")
return "\n".join(output)
def routes(self) -> str:
def routes(self) -> Optional[str]:
output = []
routes = self.get("route", [])
self.set("routes", routes)
routes: list[str] = self.get("route", [])
for gw in routes:
output.append(f"[Route]\nGateway={gw}\nGatewayOnLink=yes\nQuickAck=yes\n")
self.set("routes", output)
if len(output) == 0:
return None
return "\n".join(output)
@ -148,7 +144,8 @@ options:
type: str
device:
description:
- The name of the network device
- The name of the network device. An ! before the value matches anything but this
value.
required: false
type: str
dns:
@ -159,7 +156,11 @@ options:
required: false
type: list
dnssec:
required: true
description:
- 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
documentation:
default: []
@ -183,7 +184,20 @@ options:
type: bool
mac:
description:
- The MAC-Address of the device
- The MAC-Address of the device. An ! before the value matches anything but this
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
type: str
name:
@ -215,6 +229,11 @@ options:
elements: str
required: false
type: list
virtualization:
description:
- The virtualization type. An ! before the value matches anything but this value.
required: false
type: str
wants:
default: []
description:

197
plugins/modules/socket.py Normale Datei
Datei anzeigen

@ -0,0 +1,197 @@
#!/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
try:
from ansible_collections.sebastian.systemd.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_collections.sebastian.systemd.plugins.module_utils.module import SystemdUnitModule
from ansible_module.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_module.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
except ImportError:
from plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from plugins.module_utils.module import SystemdUnitModule, installable
from ansible_collections.sebastian.base.plugins.module_utils.generic import SYSTEMD_SERVICE_CONFIG, Types
from ansible_collections.sebastian.base.plugins.module_utils.module import SystemdReloadMixin, SystemdUnitModule, installable
@installable
class Module(SystemdUnitModule):
class Module(SystemdUnitModule, SystemdReloadMixin): # type: ignore[misc]
"""Creates System Services units"""
name = "system_service"
module_spec = dict(
argument_spec=dict(
name=Types.str(required=True),
user=Types.str(),
group=Types.str(),
name=Types.str(required=True, help="Name of the service"),
serviceuser=Types.str(help="Username of under which the commands run at.", default="root"),
servicegroup=Types.str(help="Group of under which the commands run at.", default="root"),
type=Types.str(
choices=("simple", "exec", "forking", "oneshot", "dbus", "notify", "notify-reload", "idle"),
default="simple",
@ -31,48 +31,116 @@ class Module(SystemdUnitModule):
"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.",
),
pre=Types.list(str, help="command or list of commands that are started before the main command(Types.str)"),
pre=Types.list(elements=str, help="command or list of commands that are started before the main command"),
start=Types.list(
str,
True,
elements=str,
required=True,
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."),
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):
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_stem(".service")
self.unitfile = (SYSTEMD_SERVICE_CONFIG / self.get("name")).with_suffix(".service")
self.__unit = None
if self.get("type", "simple") != "oneshot" and len(self.get("start")) > 1:
self.module.fail_json("only oneshot services are allowed to have multiple start commands", **self.result)
self.fail("only oneshot services are allowed to have multiple start commands")
def service(self):
section = "[Service]\n"
if self.get("type"):
section += "Type=".format(self.get("type"))
if self.get("pre", False):
for cmd in self.get("pre"):
section += "ExecStartPre={}".format(cmd)
if self.get("start", False):
for cmd in self.get("start"):
section += "ExecStart={}".format(cmd)
if self.get("post", False):
for cmd in self.get("post"):
section += "ExecStartPost={}".format(cmd)
if self.get("user", False):
section += "User={}".format(self.get("user"))
if self.get("group", False):
section += "Group={}".format(self.get("group"))
return section
def service(self) -> str:
params = []
if self.get("environment", False):
for env in self.get("environment"):
params.append(f"Environment={env['name']}={env['value']}\n")
params.extend(
self.map_param(
type="Type",
pre="ExecStartPre",
start="ExecStart",
stop="ExecStop",
post="ExecStartPost",
serviceuser="User",
servicegroup="Group",
workingdirectory="WorkingDirectory",
environmentfile="EnvironmentFile",
protecthome="ProtectHome",
protectsystem="ProtectSystem",
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:
if self.__unit is None:
self.__unit = "\n".join(
(
self.header(),
self.service(),
self.install(),
)
self.__unit = self._unit(
self.header(),
self.service(),
self.install(), # type: ignore[call-arg,misc]
)
return self.__unit
@ -108,12 +176,64 @@ options:
elements: str
required: false
type: list
group:
environment:
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
type: str
type: list
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:
description:
- Name of the service
required: true
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:
default: []
description:
@ -134,10 +254,37 @@ options:
pre:
default: []
description:
- command or list of commands that are started before the main command(Types.str)
- command or list of commands that are started before the main command
elements: str
required: false
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:
default: []
description:
@ -153,6 +300,44 @@ options:
elements: str
required: false
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:
description:
- command or list of commands that are started as main programm. Multiple commands
@ -160,6 +345,17 @@ options:
elements: str
required: true
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:
choices:
- simple
@ -186,9 +382,6 @@ options:
- idle is similar to simple, but it can delay the start up by a few seconds.
required: false
type: str
user:
required: false
type: str
wanted_by:
default: []
description:
@ -205,6 +398,11 @@ options:
elements: str
required: false
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
"""

Datei anzeigen

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

252
plugins/modules/timer.py Normale Datei
Datei anzeigen

@ -0,0 +1,252 @@
#!/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

@ -0,0 +1,47 @@
#!/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

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

Datei anzeigen

@ -1,39 +0,0 @@
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")

Datei anzeigen

@ -1,40 +0,0 @@
#!/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

6
upload.sh Ausführbare Datei
Datei anzeigen

@ -0,0 +1,6 @@
#!/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/*