diff --git a/.config/pycodestyle.cfg b/.config/pycodestyle.cfg index c370dd2..9660b71 100644 --- a/.config/pycodestyle.cfg +++ b/.config/pycodestyle.cfg @@ -3,5 +3,5 @@ ignore = E402, E123 # It's fine to have line-length of 99 -max-line-length = 99 +max-line-length = 150 diff --git a/.github/workflows/coredump.yml b/.github/workflows/coredump.yml index f3cdaa8..265f53f 100644 --- a/.github/workflows/coredump.yml +++ b/.github/workflows/coredump.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/coredump/**' - - '.github/workflows/test_role_coredump.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/coredump.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/coredump/**' - - '.github/workflows/test_role_coredump.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/coredump.yml' env: COLLECTION_NAMESPACE: bodsch @@ -139,6 +136,11 @@ jobs: mkdir -p /home/runner/.ansible/collections ansible-galaxy collection install community.docker --force + - name: force reinstall of community.docker + run: | + mkdir -p /home/runner/.ansible/collections + ansible-galaxy collection install community.docker --force + - name: Install collection run: | make \ diff --git a/.github/workflows/homed.yml b/.github/workflows/homed.yml index a3d463a..77af196 100644 --- a/.github/workflows/homed.yml +++ b/.github/workflows/homed.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/homed/**' - - '.github/workflows/test_role_homed.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/homed.yml' + pull_request: branches: - 'main' @@ -37,13 +36,13 @@ on: - "!Makefile" - "!README.md" - 'roles/homed/**' - - '.github/workflows/test_role_homed.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/homed.yml' env: COLLECTION_NAMESPACE: bodsch COLLECTION_NAME: systemd + PY_COLORS: '1' + ANSIBLE_FORCE_COLOR: '1' jobs: debian: diff --git a/.github/workflows/journald.yml b/.github/workflows/journald.yml index 150aa3c..f6dd938 100644 --- a/.github/workflows/journald.yml +++ b/.github/workflows/journald.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/journald/**' - - '.github/workflows/test_role_journald.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/journald.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/journald/**' - - '.github/workflows/test_role_journald.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/journald.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index cd059be..9937eae 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -22,8 +22,9 @@ on: paths: - 'plugins/**' - '!roles/**' - - '!.github/workflows/test_role*.yml' + - '!.github/workflows/*.yml' - '.config/pycodestyle.cfg' + pull_request: branches: - 'feature/**' @@ -32,7 +33,7 @@ on: paths: - 'plugins/**' - '!roles/**' - - '!.github/workflows/test_role*.yml' + - '!.github/workflows/*.yml' - '.config/pycodestyle.cfg' jobs: @@ -64,4 +65,4 @@ jobs: - name: Lint code. run: | - pycodestyle plugins/ --config=.config/pycodestyle.cfg --statistics --count + pycodestyle plugins/ --config=.config/pycodestyle.cfg --statistics --count --exclude=test_*.py --exclude=hooks/*.py diff --git a/.github/workflows/logind.yml b/.github/workflows/logind.yml index a2bb975..2c73c23 100644 --- a/.github/workflows/logind.yml +++ b/.github/workflows/logind.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/logind/**' - - '.github/workflows/test_role_logind.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/logind.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/logind/**' - - '.github/workflows/test_role_logind.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/logind.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/networkd.yml b/.github/workflows/networkd.yml index 213dde3..c92b15a 100644 --- a/.github/workflows/networkd.yml +++ b/.github/workflows/networkd.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/networkd/**' - - '.github/workflows/test_role_networkd.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/networkd.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/networkd/**' - - '.github/workflows/test_role_networkd.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/networkd.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/oomd.yml b/.github/workflows/oomd.yml index f7512bb..48ffd3d 100644 --- a/.github/workflows/oomd.yml +++ b/.github/workflows/oomd.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/oomd/**' - - '.github/workflows/test_role_oomd.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/oomd.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/oomd/**' - - '.github/workflows/test_role_oomd.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/oomd.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/pstore.yml b/.github/workflows/pstore.yml index d10aa2c..7c39840 100644 --- a/.github/workflows/pstore.yml +++ b/.github/workflows/pstore.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/pstore/**' - - '.github/workflows/test_role_pstore.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/pstore.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/pstore/**' - - '.github/workflows/test_role_pstore.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/pstore.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/resolved.yml b/.github/workflows/resolved.yml index 160c6c0..85a291b 100644 --- a/.github/workflows/resolved.yml +++ b/.github/workflows/resolved.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/resolved/**' - - '.github/workflows/test_role_resolved.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/resolved.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/resolved/**' - - '.github/workflows/test_role_resolved.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/resolved.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/sleep.yml b/.github/workflows/sleep.yml index f797e1d..90627d6 100644 --- a/.github/workflows/sleep.yml +++ b/.github/workflows/sleep.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/sleep/**' - - '.github/workflows/test_role_sleep.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/sleep.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/sleep/**' - - '.github/workflows/test_role_sleep.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/sleep.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/system.yml b/.github/workflows/system.yml index 20eb049..6287c8b 100644 --- a/.github/workflows/system.yml +++ b/.github/workflows/system.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/system/**' - - '.github/workflows/test_role_system.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/system.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/system/**' - - '.github/workflows/test_role_system.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/system.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/timesyncd.yml b/.github/workflows/timesyncd.yml index c6bf4f4..166b6cf 100644 --- a/.github/workflows/timesyncd.yml +++ b/.github/workflows/timesyncd.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/timesyncd/**' - - '.github/workflows/test_role_timesyncd.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/timesyncd.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/timesyncd/**' - - '.github/workflows/test_role_timesyncd.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/timesyncd.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/.github/workflows/user.yml b/.github/workflows/user.yml index 7f49e95..d111285 100644 --- a/.github/workflows/user.yml +++ b/.github/workflows/user.yml @@ -24,9 +24,8 @@ on: - "!Makefile" - "!README.md" - 'roles/user/**' - - '.github/workflows/test_role_user.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/user.yml' + pull_request: branches: - 'main' @@ -37,9 +36,7 @@ on: - "!Makefile" - "!README.md" - 'roles/user/**' - - '.github/workflows/test_role_user.yml' - - '.config/ansible-lint.yml' - - '.yamllint' + - '.github/workflows/user.yml' env: COLLECTION_NAMESPACE: bodsch diff --git a/README.md b/README.md index a6122c6..93c3d5f 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,27 @@ Documentation for the collection. | Role | | Description | | :---- | :---- | :---- | -| [bodsch.systemd.coredump](./roles/coredump/README.md) | | configure systemd-coredump | -| [bodsch.systemd.homed](./roles/homed/README.md) | | configure systemd-homed | -| [bodsch.systemd.journald](./roles/journald/README.md) | | configure systemd-journald | -| [bodsch.systemd.oomd](./roles/oomd/README.md) | | configure systemd-oomd | -| [bodsch.systemd.logind](./roles/logind/README.md) | | configure systemd-logind | -| [bodsch.systemd.networkd](./roles/networkd/README.md) | | configure systemd-networkd | -| [bodsch.systemd.resolved](./roles/resolved/README.md) | | configure systemd-resolved | -| [bodsch.systemd.system](./roles/system/README.md) | | configure systemd-system | -| [bodsch.systemd.timesyncd](./roles/timesyncd/README.md) | | configure systemd-timesyncd | -| [bodsch.systemd.user](./roles/user/README.md) | | configure systemd-user | +| [bodsch.systemd.coredump](./roles/coredump/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/coredump.yml?branch=main)][coredump] | configure systemd-coredump | +| [bodsch.systemd.homed](./roles/homed/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/homed.yml?branch=main)][homed] | configure systemd-homed | +| [bodsch.systemd.journald](./roles/journald/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/journald.yml?branch=main)][journald] | configure systemd-journald | +| [bodsch.systemd.oomd](./roles/oomd/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/oomd.yml?branch=main)][oomd] | configure systemd-oomd | +| [bodsch.systemd.logind](./roles/logind/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/logind.yml?branch=main)][logind] | configure systemd-logind | +| [bodsch.systemd.networkd](./roles/networkd/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/networkd.yml?branch=main)][networkd] | configure systemd-networkd | +| [bodsch.systemd.resolved](./roles/resolved/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/resolved.yml?branch=main)][resolved] | configure systemd-resolved | +| [bodsch.systemd.system](./roles/system/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/system.yml?branch=main)][system] | configure systemd-system | +| [bodsch.systemd.timesyncd](./roles/timesyncd/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/timesyncd.yml?branch=main)][timesyncd] | configure systemd-timesyncd | +| [bodsch.systemd.user](./roles/user/README.md) | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bodsch/ansible-collection-systemd/user.yml?branch=main)][user] | configure systemd-user | + +[coredump]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/coredump.vml +[homed]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/homed.vml +[journald]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/journald.vml +[oomd]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/oomd.vml +[logind]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/logind.vml +[networkd]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/networkd.vml +[resolved]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/resolved.vml +[system]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/system.vml +[timesyncd]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/timesyncd.vml +[user]: https://github.com/bodsch/ansible-collection-systemd/actions/workflows/user.vml ## Included content @@ -25,6 +36,7 @@ Documentation for the collection. | Name | Description | |:--------------------------|:----| | [journalctl](./plugins/modules/journalctl.py) | Query the systemd journal with a very limited number of possible parameters | +| [unit_file](./plugins/modules/unit_file.py) | This can be used to create a systemd unit file. The `service`, `timer` and `socket` types are supported. | ## Installing this collection diff --git a/galaxy.yml b/galaxy.yml index 2b84b92..0a5d8ef 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -3,7 +3,7 @@ namespace: bodsch name: systemd -version: 1.1.0 +version: 1.2.0 readme: README.md diff --git a/hooks/doc b/hooks/doc index c600aae..ddcbbcc 100755 --- a/hooks/doc +++ b/hooks/doc @@ -2,8 +2,6 @@ . hooks/molecule.rc -set -x - if [ -z "${COLLECTION_DIR}" ] then echo "missing collection directory" diff --git a/plugins/filter/lists.py b/plugins/filter/lists.py index 0e3a5cb..689ce62 100644 --- a/plugins/filter/lists.py +++ b/plugins/filter/lists.py @@ -10,6 +10,7 @@ class FilterModule(object): """ """ + def filters(self): return { 'valid_list': self.valid_list, diff --git a/plugins/modules/journalctl.py b/plugins/modules/journalctl.py index cb93283..3119ed0 100644 --- a/plugins/modules/journalctl.py +++ b/plugins/modules/journalctl.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/python3 # -*- coding: utf-8 -*- # (c) 2020-2023, Bodo Schulz @@ -12,7 +12,7 @@ DOCUMENTATION = """ module: journalctl author: - - Bodo 'bodsch' Schulz + - Bodo 'bodsch' Schulz (@bodsch) short_description: Query the systemd journal with a very limited number of possible parameters. version_added: 1.1.0 @@ -105,15 +105,6 @@ def __init__(self, module): self.reverse = module.params.get("reverse") self.arguments = module.params.get("arguments") - # module.log(msg="----------------------------") - # module.log(msg=f" journalctl : {self._journalctl}") - # module.log(msg=f" unit : {self.unit}") - # module.log(msg=f" identifier : {self.identifier}") - # module.log(msg=f" lines : {self.lines}") - # module.log(msg=f" reverse : {self.reverse}") - # module.log(msg=f" arguments : {self.arguments}") - # module.log(msg="----------------------------") - def run(self): """ """ @@ -214,7 +205,7 @@ def main(): k = JournalCtl(module) result = k.run() - module.log(msg=f"= result: {result}") + # module.log(msg=f"= result: {result}") module.exit_json(**result) diff --git a/plugins/modules/unit_file.py b/plugins/modules/unit_file.py new file mode 100644 index 0000000..9a7b915 --- /dev/null +++ b/plugins/modules/unit_file.py @@ -0,0 +1,452 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# (c) 2020-2023, Bodo Schulz +# Apache-2.0 (see LICENSE or https://opensource.org/license/apache-2-0) +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import absolute_import, division, print_function + +import os +import shutil + +from ansible_collections.bodsch.core.plugins.module_utils.directory import create_directory +from ansible_collections.bodsch.core.plugins.module_utils.checksum import Checksum + +from ansible.module_utils.basic import AnsibleModule + + +DOCUMENTATION = """ +module: unit_file +author: + - Bodo 'bodsch' Schulz (@bodsch) +short_description: Creates a systemd unit file. +version_added: 1.2.0 + +description: + - Creates a systemd unit file. + - The `service`, `timer` and `socket` types are supported. + +options: + unit_type: + description: + - The unit type to be created. + - The (C(service), C(timer) and C(socket)) types are supported. + - Default is C(service). + type: str + choices: [ service, timer, socket ] + name: + description: + - The name of the unit file. + type: str + state: + description: + - Whether to install (C(present) or remove (C(absent) a unit file. + - Default is C(present). + type: str + choices: [ absent, present ] + drop_ins: + description: + - A list of possible systemd drop-ins. + - The structure corresponds exactly to that of a normal unit file. + type: list + unit_file: + description: The content of a systemd unit file. + type: dict + +""" + +EXAMPLES = """ +- name: create getty drop-ins + bodsch.systemd.unit_file: + name: "getty@tty1" + state: "present" + unit_type: "service" + drop_ins: + - name: autologin + state: present + service: + ExecStart: + - "" + - "{% raw %}-/sbin/agetty -o '-p -f -- \\\\u' --noclear --autologin username %I $TERM{% endraw %}" + Type: simple + + - name: noclear + state: absent + service: + TTYVTDisallocate: false + when: + - ansible_service_mgr == 'systemd' + +- name: create systemd unit file + bodsch.systemd.unit_file: + name: "vaultwarden" + state: "present" + unit_type: "service" + unit_file: + description: | + # + # + # + # + # + # + + Unit: + Description: Vaultwarden API server + Documentation: https://github.com/dani-garcia/vaultwarden + After: network.target + Service: + Type: simple + User: vaultwarden + Group: vaultwarden + LimitNOFILE: 1048576 + UMask: "0077" + + ExecStart: /usr/bin/vaultwarden + EnvironmentFile: /etc/vaultwarden/config.env + Install: + WantedBy: multi-user.target + when: + - ansible_service_mgr == 'systemd' + +- name: create systemd socket + bodsch.systemd.unit_file: + name: "systemd-initctl" + state: "present" + unit_type: "socket" + unit_file: + description: | + # SPDX-License-Identifier: LGPL-2.1-or-later + # + # This file is part of systemd. + # + # systemd is free software; you can redistribute it and/or modify it + # under the terms of the GNU Lesser General Public License as published by + # the Free Software Foundation; either version 2.1 of the License, or + # (at your option) any later version. + unit: + Description: initctl Compatibility Named Pipe + Documentation: man:systemd-initctl.socket(8) + DefaultDependencies: no + Before: sockets.target + + Socket: + ListenFIFO: /run/initctl + Symlinks: /dev/initctl + SocketMode: "0600" + when: + - ansible_service_mgr == 'systemd' + + +- name: create systemd timer + bodsch.systemd.unit_file: + name: "systemd-tmpfiles-clean" + state: "present" + unit_type: "timer" + unit_file: + unit: + Description: Daily Cleanup of Temporary Directories + Documentation: man:tmpfiles.d(5) man:systemd-tmpfiles(8) + ConditionPathExists: "!/etc/initrd-release" + timer: + OnBootSec: 15min + OnUnitActiveSec: 1d + when: + - ansible_service_mgr == 'systemd' +""" + +RETURN = """ +changed: + type: bool + description: Status of the action +msg: + type: str + description: Readable edition of what has been done. +""" + +# -------------------------------------------------------------------------------------------------- + +UNIT_TPL = """# generated by ansible + +{% if item.description is defined %} +{{ item.description }} +{% set _ = item.pop('description') %} +{% endif %} +{% for section, options in item.items() %} +[{{ section | capitalize }}] + {% for option, values in options.items() %} + {% if values is string or values is number %} +{{ option.ljust(34) }} = {{ values }} + {% else %} + {% for value in values %} +{{ option.ljust(34) }} = {{ value }} + {% endfor %} + {% endif %} + {% endfor %} + +{% endfor %} +{# +#} +""" + + +class SystemdUnitFile(object): + """ + """ + module = None + + def __init__(self, module): + """ + """ + self.module = module + + self.unit_type = module.params.get("unit_type") + self.name = module.params.get("name") + self.state = module.params.get("state") + self.overwrite = module.params.get("overwrite") + self.drop_ins = module.params.get("drop_ins") + self.unit_file = module.params.get("unit_file") + + self.tmp_directory = os.path.join("/run/.ansible", f"systemd_unit.{str(os.getpid())}") + + def run(self): + """ + """ + result = dict( + rc=1, + failed=True, + changed=False, + ) + + self.checksum = Checksum(self.module) + + if self.state == "absent": + result = self.clean_unit_files() + + else: + result = self.create_unit_files() + + if os.path.exists(self.tmp_directory): + shutil.rmtree(self.tmp_directory) + + return result + + def create_unit_files(self): + """ """ + if isinstance(self.drop_ins, list) and len(self.drop_ins) > 0: + + create_directory(directory=self.tmp_directory, mode="0750") + result = self.create_drop_in(self.drop_ins) + + if isinstance(self.unit_file, dict) and len(self.unit_file) > 0: + + create_directory(directory=self.tmp_directory, mode="0750") + result = self.create_unit_file(self.unit_file) + + return result + + def clean_unit_files(self): + """ """ + if isinstance(self.drop_ins, list) and len(self.drop_ins) > 0: + + service_name = f"/etc/systemd/system/{self.name}.d" + + for drop_in in self.drop_ins: + name = drop_in.get("name") + state = drop_in.get("state", "present") + + if state == "absent": + unit_file = os.path.join(service_name, f"{name}.conf") + result = self.__remove_unit(unit_file) + + if isinstance(self.unit_file, dict) and len(self.unit_file) > 0: + unit_file = os.path.join("/lib/systemd/system", f"{self.name}.{self.unit_type}") + result = self.__remove_unit(unit_file) + + return result + + def create_drop_in(self, data): + """ + """ + service_name = f"/etc/systemd/system/{self.name}.d" + + if not os.path.exists(service_name): + create_directory(service_name) + + for drop_in in data: + name = drop_in.get("name") + state = drop_in.get("state", "present") + + unit_file = os.path.join(service_name, f"{name}.conf") + file_temporary = os.path.join(self.tmp_directory, f"{name}.conf") + + if state == "present": + file_temporary = os.path.join(self.tmp_directory, f"{name}.conf") + + data = self.__template(drop_in) + + with open(file_temporary, "w") as f: + f.write(data) + + result = self.__changed(file_temporary, unit_file) + + else: + result = self.__remove_unit(unit_file) + + return result + + def create_unit_file(self, data): + """ + """ + unit_file = os.path.join("/lib/systemd/system", f"{self.name}.{self.unit_type}") + file_temporary = os.path.join(self.tmp_directory, f"{self.name}.{self.unit_type}") + + if self.state == "present": + data = self.__template(data) + + with open(file_temporary, "w") as f: + f.write(data) + + result = self.__changed(file_temporary, unit_file) + else: + pass + + result = dict( + changed=False, + failed=False + ) + + return result + + def __changed(self, file_temporary, unit_file): + """ """ + + old_checksum = self.checksum.checksum_from_file(unit_file) + new_checksum = self.checksum.checksum_from_file(file_temporary) + + changed = not (new_checksum == old_checksum) + new_file = False + msg = "The unit-file has not been changed" + + # self.module.log(msg=f" file_name {unit_file}") + # self.module.log(msg=f" file_temporary {file_temporary}") + # self.module.log(msg=f" old_checksum {old_checksum}") + # self.module.log(msg=f" new_checksum {new_checksum}") + # self.module.log(msg=f" changed {changed}") + + if changed: + new_file = (old_checksum is None) + shutil.move(file_temporary, unit_file) + msg = "The unit-file was successfully changed" + + if new_file: + msg = "The unit-file was successfully created" + + result = dict( + changed=changed, + msg=msg + ) + + return result + + def __remove_unit(self, unit_file): + """ """ + if os.path.exists(unit_file): + os.remove(unit_file) + + result = dict( + changed=True, + failed=False, + msg="The unit-file was successfully removed." + ) + else: + result = dict( + changed=False, + failed=False, + msg="The unit-file has already been removed." + ) + + return result + + def __template(self, data): + """ + """ + # self.module.log(msg=f"__template({data} ({type(data)}))") + + if isinstance(data, dict): + from jinja2 import Template + + if data.get('name'): + _ = data.pop('name') + if data.get('state'): + _ = data.pop('state') + + # self.module.log(msg=f"{data} ({type(data)})") + + tm = Template(UNIT_TPL, trim_blocks=True, lstrip_blocks=True) + d = tm.render(item=data) + else: + d = None + + return d + + +def main(): + """ + """ + args = dict( + unit_type=dict( + choose=[ + "service", + "socket", + "timer" + ], + default="service", + type="str" + ), + name=dict( + required=True, + type="str" + ), + state=dict( + choose=[ + "absent", + "present", + ], + default="present", + type="str" + ), + overwrite=dict( + required=False, + default=False, + type="bool" + ), + drop_ins=dict( + required=False, + default=[], + type=list + ), + unit_file=dict( + required=False, + default={}, + type=dict + ), + ) + + module = AnsibleModule( + argument_spec=args, + supports_check_mode=False, + ) + + k = SystemdUnitFile(module) + result = k.run() + + # module.log(msg=f"= result: {result}") + + module.exit_json(**result) + + +# import module snippets +if __name__ == "__main__": + main() diff --git a/roles/resolved/molecule/default/group_vars/all/vars.yml b/roles/resolved/molecule/default/group_vars/all/vars.yml index 9ac822a..5a99a2e 100644 --- a/roles/resolved/molecule/default/group_vars/all/vars.yml +++ b/roles/resolved/molecule/default/group_vars/all/vars.yml @@ -1,12 +1,9 @@ --- -alertmanager_route: - group_by: - - 'alertname' - - 'service' - group_wait: 30s - group_interval: 5m - repeat_interval: 4h - default_receiver: blackhole +systemd_resolved: + dns: + - "1.1.1.1" + - "9.9.9.9" + ... diff --git a/roles/systemd_unit/.ansible-lint b/roles/systemd_unit/.ansible-lint new file mode 100644 index 0000000..5343e85 --- /dev/null +++ b/roles/systemd_unit/.ansible-lint @@ -0,0 +1,6 @@ +--- + +skip_list: + - name[casing] + - name[template] + - syntax-check[specific] diff --git a/roles/systemd_unit/.editorconfig b/roles/systemd_unit/.editorconfig new file mode 100644 index 0000000..898cdbd --- /dev/null +++ b/roles/systemd_unit/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# https://editorconfig.org/ + + +root = true + +[*] +indent_style = space +indent_size = 2 + +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 100 + +[*.py] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/roles/systemd_unit/.flake8 b/roles/systemd_unit/.flake8 new file mode 100644 index 0000000..1962f7e --- /dev/null +++ b/roles/systemd_unit/.flake8 @@ -0,0 +1,19 @@ +[flake8] + +# E221 multiple spaces before operator +# E251 unexpected spaces around keyword / parameter equals + +ignore = E221,E251 + +exclude = + # No need to traverse our git directory + .git, + # There's no value in checking cache directories + __pycache__, + .tox + +# E203: https://github.com/python/black/issues/315 +# ignore = D,E741,W503,W504,H,E501,E203 + +max-line-length = 195 + diff --git a/roles/systemd_unit/.gitignore b/roles/systemd_unit/.gitignore new file mode 100644 index 0000000..3d35e6a --- /dev/null +++ b/roles/systemd_unit/.gitignore @@ -0,0 +1,6 @@ +.tox +.galaxy_install_info +*kate-swp +__pycache__ +.cache +.directory diff --git a/roles/systemd_unit/.yamllint b/roles/systemd_unit/.yamllint new file mode 100644 index 0000000..e3f52af --- /dev/null +++ b/roles/systemd_unit/.yamllint @@ -0,0 +1,36 @@ +--- +# Based on ansible-lint config +extends: default + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + colons: + max-spaces-after: -1 + level: error + commas: + max-spaces-after: -1 + level: error + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: + max: 3 + level: error + hyphens: + level: error + indentation: + spaces: 2 + key-duplicates: enable + line-length: + max: 195 + level: warning + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable + truthy: disable diff --git a/roles/systemd_unit/CONTRIBUTING.md b/roles/systemd_unit/CONTRIBUTING.md new file mode 100644 index 0000000..e3cd4cc --- /dev/null +++ b/roles/systemd_unit/CONTRIBUTING.md @@ -0,0 +1,31 @@ +Contributing +============ +If you want to contribute to a project and make it better, your help is very welcome. +Contributing is also a great way to learn more about social coding on Github, new technologies and +and their ecosystems and how to make constructive, helpful bug reports, feature requests and the +noblest of all contributions: a good, clean pull request. + +### How to make a clean pull request + +Look for a project's contribution instructions. If there are any, follow them. + +- Create a personal fork of the project on Github. +- Clone the fork on your local machine. Your remote repo on Github is called `origin`. +- Add the original repository as a remote called `upstream`. +- If you created your fork a while ago be sure to pull upstream changes into your local repository. +- Create a new branch to work on! Branch from `develop` if it exists, else from `master`. +- Implement/fix your feature, comment your code. +- Follow the code style of the project, including indentation. +- If the project has tests run them! +- Write or adapt tests as needed. +- Add or change the documentation as needed. +- Squash your commits into a single commit. Create a new branch if necessary. +- Push your branch to your fork on Github, the remote `origin`. +- From your fork open a pull request in the correct branch. Target the project's `develop` branch if there is one, else go for `master`! +- If the maintainer requests further changes just push them to your branch. The PR will be updated automatically. +- Once the pull request is approved and merged you can pull the changes from `upstream` to your local repo and delete + your extra branch(es). + +And last but not least: Always write your commit messages in the present tense. +Your commit message should describe what the commit, when applied, does to the +code – not what you did to the code. diff --git a/roles/systemd_unit/LICENSE b/roles/systemd_unit/LICENSE new file mode 100644 index 0000000..8c8472f --- /dev/null +++ b/roles/systemd_unit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020-2021 Bodo Schulz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/roles/systemd_unit/Makefile b/roles/systemd_unit/Makefile new file mode 100644 index 0000000..3abaf48 --- /dev/null +++ b/roles/systemd_unit/Makefile @@ -0,0 +1,22 @@ +# +export TOX_SCENARIO ?= default +export TOX_ANSIBLE ?= ansible_6.1 + +.PHONY: converge destroy verify test lint + +default: converge + +converge: + @hooks/converge + +destroy: + @hooks/destroy + +verify: + @hooks/verify + +test: + @hooks/test + +lint: + @hooks/lint diff --git a/roles/systemd_unit/README.md b/roles/systemd_unit/README.md new file mode 100644 index 0000000..e69de29 diff --git a/roles/systemd_unit/defaults/main.yml b/roles/systemd_unit/defaults/main.yml new file mode 100644 index 0000000..83b94ed --- /dev/null +++ b/roles/systemd_unit/defaults/main.yml @@ -0,0 +1,5 @@ +--- + +systemd_unit: [] + +... diff --git a/roles/systemd_unit/hooks/converge b/roles/systemd_unit/hooks/converge new file mode 100755 index 0000000..0c50932 --- /dev/null +++ b/roles/systemd_unit/hooks/converge @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +hooks/tox.sh "converge" diff --git a/roles/systemd_unit/hooks/destroy b/roles/systemd_unit/hooks/destroy new file mode 100755 index 0000000..b4a3f8d --- /dev/null +++ b/roles/systemd_unit/hooks/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +hooks/tox.sh "destroy" diff --git a/roles/systemd_unit/hooks/lint b/roles/systemd_unit/hooks/lint new file mode 100755 index 0000000..ef226a0 --- /dev/null +++ b/roles/systemd_unit/hooks/lint @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +hooks/tox.sh "lint" diff --git a/roles/systemd_unit/hooks/molecule.rc b/roles/systemd_unit/hooks/molecule.rc new file mode 100644 index 0000000..a15f7c3 --- /dev/null +++ b/roles/systemd_unit/hooks/molecule.rc @@ -0,0 +1,9 @@ + +TOX_ARGS= + +if [ -n "${TOX_SCENARIO}" ] +then + TOX_ARGS="--scenario-name ${TOX_SCENARIO}" +fi + +TOX_OPTS="-e ${TOX_ANSIBLE}" diff --git a/roles/systemd_unit/hooks/test b/roles/systemd_unit/hooks/test new file mode 100755 index 0000000..2869139 --- /dev/null +++ b/roles/systemd_unit/hooks/test @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +hooks/tox.sh "test" diff --git a/roles/systemd_unit/hooks/tox.sh b/roles/systemd_unit/hooks/tox.sh new file mode 100755 index 0000000..62bb777 --- /dev/null +++ b/roles/systemd_unit/hooks/tox.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +. hooks/molecule.rc + +TOX_TEST="${1}" + +if [ -f "./collections.yml" ] +then + for collection in $(grep -v "#" collections.yml | grep "^ - name: " | awk -F ': ' '{print $2}') + do + collections_installed="$(ansible-galaxy collection list | grep ${collection} 2> /dev/null)" + + if [ -z "${collections_installed}" ] + then + echo "Install the required collection '${collection}'" + ansible-galaxy collection install ${collection} + else + collection_version=$(echo "${collections_installed}" | awk -F ' ' '{print $2}') + + echo "The required collection '${collection}' is installed in version ${collection_version}." + fi + done + echo "" +fi + +tox ${TOX_OPTS} -- molecule ${TOX_TEST} ${TOX_ARGS} diff --git a/roles/systemd_unit/hooks/verify b/roles/systemd_unit/hooks/verify new file mode 100755 index 0000000..5f436af --- /dev/null +++ b/roles/systemd_unit/hooks/verify @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +hooks/tox.sh "verify" diff --git a/roles/systemd_unit/molecule/default/converge.yml b/roles/systemd_unit/molecule/default/converge.yml new file mode 100644 index 0000000..fa8cdb5 --- /dev/null +++ b/roles/systemd_unit/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- + +- name: converge + hosts: instance + any_errors_fatal: false + + roles: + - role: bodsch.systemd.systemd_unit diff --git a/roles/systemd_unit/molecule/default/group_vars/all/vars.yml b/roles/systemd_unit/molecule/default/group_vars/all/vars.yml new file mode 100644 index 0000000..e13386b --- /dev/null +++ b/roles/systemd_unit/molecule/default/group_vars/all/vars.yml @@ -0,0 +1,75 @@ +--- + +systemd_unit: + - name: vaultwarden + state: absent + unit_type: service + overwrite: false + unit_file: + description: | + # + # + # + # + # + # + + Unit: + Description: Vaultwarden API server + Documentation: https://github.com/dani-garcia/vaultwarden + After: network.target + + Service: + Type: simple + User: vaultwarden + Group: vaultwarden + LimitNOFILE: 1048576 + UMask: "0077" + + ExecStart: /usr/bin/vaultwarden + EnvironmentFile: /etc/vaultwarden/config.env + + Restart: on-failure + RestartSec: 15s + + CapabilityBoundingSet: CAP_NET_BIND_SERVICE + AmbientCapabilities: CAP_NET_BIND_SERVICE + + LockPersonality: true + MemoryDenyWriteExecute: true + PrivateDevices: true + PrivateTmp: true + ProtectClock: true + ProtectControlGroups: true + ProtectHome: true + ProtectHostname: true + ProtectKernelLogs: true + ProtectKernelModules: true + ProtectKernelTunables: true + ProtectSystem: strict + RemoveIPC: true + RestrictAddressFamilies: AF_UNIX AF_INET AF_INET6 + RestrictNamespaces: true + RestrictRealtime: true + RestrictSUIDSGID: true + + NoNewPrivileges: true + + SystemCallFilter: + - "@system-service" + - "~@privileged @resources" + SystemCallArchitectures: native + + WorkingDirectory: /var/lib/vaultwarden + ReadWriteDirectories: /var/lib/vaultwarden + # ReadWriteDirectories: {{ vaultwarden_config.logging.log_file | dirname }} + # {% if vaultwarden_config.web_vault.enabled is defined and + # vaultwarden_config.web_vault.enabled | string | length > 0 and + # vaultwarden_config.web_vault.enabled | bool == True %} + # ReadWriteDirectories: {{ vaultwarden_config.directories.web_vault }}/web-vault + # {% endif %} + + Install: + WantedBy: multi-user.target + +... diff --git a/roles/systemd_unit/molecule/default/molecule.yml b/roles/systemd_unit/molecule/default/molecule.yml new file mode 100644 index 0000000..ba28a37 --- /dev/null +++ b/roles/systemd_unit/molecule/default/molecule.yml @@ -0,0 +1,56 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +platforms: + - name: instance + image: "bodsch/ansible-${DISTRIBUTION:-debian:12}" + command: ${MOLECULE_DOCKER_COMMAND:-""} + docker_host: "${DOCKER_HOST:-unix://run/docker.sock}" + privileged: true + pre_build_image: true + cgroupns_mode: host + mounts: + - source: /sys/fs/cgroup + target: /sys/fs/cgroup + type: bind + read_only: false + volumes: + - /var/lib/containerd + capabilities: + - SYS_ADMIN + tmpfs: + - /run + - /tmp + +provisioner: + name: ansible + ansible_args: + - --diff + - -v + config_options: + defaults: + deprecation_warnings: true + stdout_callback: yaml + callbacks_enabled: profile_tasks + gathering: smart + fact_caching: jsonfile + fact_caching_timeout: 8640 + fact_caching_connection: ansible_facts + +scenario: + test_sequence: + - destroy + - dependency + - syntax + - create + - prepare + - converge + - verify + - destroy + +verifier: + name: testinfra diff --git a/roles/systemd_unit/molecule/default/prepare.yml b/roles/systemd_unit/molecule/default/prepare.yml new file mode 100644 index 0000000..edd82c8 --- /dev/null +++ b/roles/systemd_unit/molecule/default/prepare.yml @@ -0,0 +1,48 @@ +--- + +- name: information + hosts: all + gather_facts: true + + pre_tasks: + - name: arch- / artixlinux + when: + - ansible_distribution | lower == 'archlinux' or + ansible_os_family | lower == 'artix linux' + block: + - name: update pacman system + ansible.builtin.command: | + pacman --refresh --sync --sysupgrade --noconfirm + register: pacman + changed_when: pacman.rc != 0 + failed_when: pacman.rc != 0 + + - name: create depends service + ansible.builtin.copy: + mode: 0755 + dest: /etc/init.d/net + content: | + #!/usr/bin/openrc-run + true + when: + - ansible_os_family | lower == 'artix linux' + + - name: update package cache + become: true + ansible.builtin.package: + update_cache: true + + - name: environment + ansible.builtin.debug: + msg: + - "os family : {{ ansible_distribution }} ({{ ansible_os_family }})" + - "distribution version : {{ ansible_distribution_major_version }}" + - "ansible version : {{ ansible_version.full }}" + - "python version : {{ ansible_python.version.major }}.{{ ansible_python.version.minor }}" + +- name: converge + hosts: instance + any_errors_fatal: false + + roles: + - role: bodsch.systemd.system diff --git a/roles/systemd_unit/molecule/default/tests/test_default.py b/roles/systemd_unit/molecule/default/tests/test_default.py new file mode 100644 index 0000000..422e777 --- /dev/null +++ b/roles/systemd_unit/molecule/default/tests/test_default.py @@ -0,0 +1,125 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from ansible.parsing.dataloader import DataLoader +from ansible.template import Templar + +import json +import pytest +import os + +import testinfra.utils.ansible_runner + +HOST = 'instance' + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts(HOST) + + +def pp_json(json_thing, sort=True, indents=2): + if type(json_thing) is str: + print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) + else: + print(json.dumps(json_thing, sort_keys=sort, indent=indents)) + return None + + +def base_directory(): + """ + """ + cwd = os.getcwd() + + if 'group_vars' in os.listdir(cwd): + directory = "../.." + molecule_directory = "." + else: + directory = "." + molecule_directory = f"molecule/{os.environ.get('MOLECULE_SCENARIO_NAME')}" + + return directory, molecule_directory + + +def read_ansible_yaml(file_name, role_name): + """ + """ + read_file = None + + for e in ["yml", "yaml"]: + test_file = f"{file_name}.{e}" + if os.path.isfile(test_file): + read_file = test_file + break + + return f"file={read_file} name={role_name}" + + +@pytest.fixture() +def get_vars(host): + """ + parse ansible variables + - defaults/main.yml + - vars/main.yml + - vars/${DISTRIBUTION}.yaml + - molecule/${MOLECULE_SCENARIO_NAME}/group_vars/all/vars.yml + """ + base_dir, molecule_dir = base_directory() + distribution = host.system_info.distribution + operation_system = None + + if distribution in ['debian', 'ubuntu']: + operation_system = "debian" + elif distribution in ['redhat', 'ol', 'centos', 'rocky', 'almalinux']: + operation_system = "redhat" + elif distribution in ['arch', 'artix']: + operation_system = f"{distribution}linux" + + # print(" -> {} / {}".format(distribution, os)) + # print(" -> {}".format(base_dir)) + + file_defaults = read_ansible_yaml(f"{base_dir}/defaults/main", "role_defaults") + file_vars = read_ansible_yaml(f"{base_dir}/vars/main", "role_vars") + file_distibution = read_ansible_yaml(f"{base_dir}/vars/{operation_system}", "role_distibution") + file_molecule = read_ansible_yaml(f"{molecule_dir}/group_vars/all/vars", "test_vars") + # file_host_molecule = read_ansible_yaml("{}/host_vars/{}/vars".format(base_dir, HOST), "host_vars") + + defaults_vars = host.ansible("include_vars", file_defaults).get("ansible_facts").get("role_defaults") + vars_vars = host.ansible("include_vars", file_vars).get("ansible_facts").get("role_vars") + distibution_vars = host.ansible("include_vars", file_distibution).get("ansible_facts").get("role_distibution") + molecule_vars = host.ansible("include_vars", file_molecule).get("ansible_facts").get("test_vars") + # host_vars = host.ansible("include_vars", file_host_molecule).get("ansible_facts").get("host_vars") + + ansible_vars = defaults_vars + ansible_vars.update(vars_vars) + ansible_vars.update(distibution_vars) + ansible_vars.update(molecule_vars) + # ansible_vars.update(host_vars) + + templar = Templar(loader=DataLoader(), variables=ansible_vars) + result = templar.template(ansible_vars, fail_on_undefined=False) + + return result + + +def local_facts(host): + """ + return local facts + """ + return host.ansible("setup").get("ansible_facts").get("ansible_local").get("system") + + +@pytest.mark.parametrize("directories", [ + "/etc/systemd", +]) +def test_directories(host, directories): + d = host.file(directories) + assert d.is_directory + + +@pytest.mark.parametrize("files", [ + "/etc/systemd/system.conf", +]) +def test_systemd_files(host, files): + """ + """ + d = host.file(files) + assert d.is_file diff --git a/roles/systemd_unit/tasks/main.yml b/roles/systemd_unit/tasks/main.yml new file mode 100644 index 0000000..93592eb --- /dev/null +++ b/roles/systemd_unit/tasks/main.yml @@ -0,0 +1,78 @@ +--- + +- name: create getty drop-ins + bodsch.systemd.unit_file: + name: "getty@tty1" + state: "present" + unit_type: "service" + drop_ins: + - name: autologin + state: present + service: + ExecStart: + - "" + - "{% raw %}-/sbin/agetty -o '-p -f -- \\\\u' --noclear --autologin username %I $TERM{% endraw %}" + Type: simple + + - name: noclear + state: absent + service: + TTYVTDisallocate: false + when: + - ansible_service_mgr == 'systemd' + +- name: create systemd unit + bodsch.systemd.unit_file: + name: "nextcloud-cron" + state: "present" + unit_type: "service" + unit_file: + unit: + Description: Nextcloud cron.php job + service: + User: www-data + ExecCondition: php -f /var/www/nextcloud/server/occ status --exit-code + ExecStart: /usr/bin/php -f /var/www/nextcloud/server/cron.php + KillMode: process + when: + - ansible_service_mgr == 'systemd' + +- name: create systemd timer + bodsch.systemd.unit_file: + name: "nextcloud-cron" + state: "present" + unit_type: "timer" + unit_file: + unit: + Description: Run Nextcloud cron.php every 5 minutes + timer: + OnBootSec: 5min + OnUnitActiveSec: 5min + Unit: nextcloud-cron.service + install: + WantedBy: timers.target + when: + - ansible_service_mgr == 'systemd' + +- name: create systemd unit files + bodsch.systemd.unit_file: + name: "{{ item.name }}" + state: "{{ item.state }}" + unit_type: "{{ item.unit_type }}" + overwrite: "{{ item.overwrite | default(omit) }}" + drop_ins: "{{ item.drop_ins | default(omit) }}" + unit_file: "{{ item.unit_file | default(omit) }}" + loop: + "{{ systemd_unit }}" + loop_control: + label: "{{ item.name }}" + register: systemd_unit_file + ignore_errors: true + when: + - systemd_unit | count > 0 + +# - name: d +# debug: +# msg: "{{ systemd_unit_file }}" + +... diff --git a/roles/systemd_unit/test-requirements.txt b/roles/systemd_unit/test-requirements.txt new file mode 100644 index 0000000..dc5c9a1 --- /dev/null +++ b/roles/systemd_unit/test-requirements.txt @@ -0,0 +1,11 @@ +ansible-lint +docker +dnspython +flake8 +molecule +molecule-plugins[docker] +netaddr +pytest-testinfra +tox +tox-gh-actions +yamllint diff --git a/roles/systemd_unit/tox.ini b/roles/systemd_unit/tox.ini new file mode 100644 index 0000000..c3099d3 --- /dev/null +++ b/roles/systemd_unit/tox.ini @@ -0,0 +1,39 @@ +[tox] +ignore_basepython_conflict = True +skip_missing_interpreters = True + +minversion = 3.25 +toxworkdir = /tmp/.tox/ + +skipsdist = true + +[testenv] +passenv = * + +# allowlist_externals = +# /usr/bin/find +# /bin/sh +# rm + +deps = + -r test-requirements.txt + ansible_4.10: ansible>=4.10,<4.11 + ansible_5.1: ansible>=5.1,<5.2 + ansible_5.2: ansible>=5.2,<5.3 + ansible_5.10: ansible>=5.10,<5.11 + ansible_6.1: ansible>=6.1,<6.2 + ansible_6.7: ansible>=6.7,<6.8 + ansible_7.0: ansible>=7.0,<7.1 + ansible_7.5: ansible>=7.5,<7.6 + ansible_8.0: ansible>=8.0,<8.1 + ansible_8.5: ansible>=8.5,<8.6 + ansible_9.0: ansible>=9.0,<9.1 + ansible_9.5: ansible>=9.5,<9.6 + ansible_10.0: ansible>=10.0,<10.1 + +#commands_pre = +# /usr/bin/find {toxinidir} -type f -not -path '{toxworkdir}/*' -path '*/__pycache__/*' -name '*.py[c|o]' -delete +# /bin/sh -c '/usr/bin/find {homedir}/.cache -type d -path "*/molecule_*" -exec rm -rfv \{\} +;' + +commands = + {posargs:molecule test --all --destroy always} diff --git a/roles/systemd_unit/vars/main.yml b/roles/systemd_unit/vars/main.yml new file mode 100644 index 0000000..6a63a96 --- /dev/null +++ b/roles/systemd_unit/vars/main.yml @@ -0,0 +1,5 @@ +--- + +systemd_defaults_unit: [] + +...