Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new systemd_info module #9764

Merged
merged 13 commits into from
Feb 23, 2025
Merged

Conversation

NomakCooper
Copy link
Contributor

@NomakCooper NomakCooper commented Feb 17, 2025

SUMMARY

This PR adds a new module, systemd_info, to the community.general collection.

I decided to develop the systemd_info module to give all Ansible users the ability to gather detailed information about systemd units ( service,target,socket,mount ).

The module gathers detailed systemd unit ( service,target,socket,mount ) info using the systemctl command.

It supports both global and select modes, and includes the ability to collect additional properties as specified by the user.

Key Features

  • Global and Select Modes:

    • Global mode: Retrieves info for all systemd units using systemctl.
    •   $ systemctl list-units --no-pager --type service,target,socket,mount --all --plain --no-legend
      
    •   $ systemctl show 'UNIT_NAME' -p 'PROPERTIES_LIST'
      
    • Select mode: Processes only the units specified in the unitname parameter. The module first verifies unit existence and, if not found, module fail.
  • Customizable Properties:

    • The extra_properties parameter allows users to request extra unit properties beyond the defaults.
  • Detailed Return Values:

    • The user must use the register function to access the return values.

Usage Examples

# Gather info for all systemd services, targets, sockets and mount
- name: Gather all systemd unit info
  community.general.systemd_info:
  register: results

# Gather info for selected units with extra properties.
- name: Gather info for selected unit(s)
  community.general.systemd_info:
    unitname:
      - systemd-journald.service
      - systemd-journald.socket
      - sshd-keygen.target
      - -.mount
    extra_properties:
        - Description
    register: results

Return Examples

  "results.units": {
    "-.mount": {
        "activestate": "active",
        "description": "Root Mount",
        "loadstate": "loaded",
        "name": "-.mount",
        "options": "rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota",
        "substate": "mounted",
        "type": "xfs",
        "what": "/dev/mapper/cs-root",
        "where": "/"
    },
    "sshd-keygen.target": {
        "activestate": "active",
        "description": "sshd-keygen.target",
        "fragmentpath": "/usr/lib/systemd/system/sshd-keygen.target",
        "loadstate": "loaded",
        "name": "sshd-keygen.target",
        "substate": "active",
        "unitfilepreset": "disabled",
        "unitfilestate": "static"
    },
    "systemd-journald.service": {
        "activestate": "active",
        "description": "Journal Service",
        "execmainpid": "613",
        "fragmentpath": "/usr/lib/systemd/system/systemd-journald.service",
        "loadstate": "loaded",
        "mainpid": "613",
        "name": "systemd-journald.service",
        "substate": "running",
        "unitfilepreset": "disabled",
        "unitfilestate": "static"
    },
    "systemd-journald.socket": {
        "activestate": "active",
        "description": "Journal Socket",
        "fragmentpath": "/usr/lib/systemd/system/systemd-journald.socket",
        "loadstate": "loaded",
        "name": "systemd-journald.socket",
        "substate": "running",
        "unitfilepreset": "disabled",
        "unitfilestate": "static"
    }
ISSUE TYPE
  • New Module/Plugin Pull Request
COMPONENT NAME

systemd_info

ADDITIONAL INFORMATION

Base Properties table

Property .service .target .socket .mount
name Yes Yes Yes Yes
loadstate Yes Yes Yes Yes
activestate Yes Yes Yes Yes
substate Yes Yes Yes Yes
fragmentpath Yes (if available) Yes (if available) Yes (if available) No
unitfilestate Yes (if available) Yes (if available) Yes (if available) No
unitfilepreset Yes (if available) Yes (if available) Yes (if available) No
mainpid Yes No No No
execmainpid Yes No No No
Where No No No Yes
What No No No Yes
Options No No No Yes
Type No No No Yes

ALL possibile unit properties can be collected by user using unitname/extra_properties options

In this PR:

  • Added the systemd_info module in plugins/modules/systemd_info.py.
  • Added an integration test in tests/integration/targets/systemd_info/.
  • Added an entry in .github/BOTMETA.yml.

Ansible sanity and integration tests were successfully run locally using Docker.

@ansibullbot ansibullbot added integration tests/integration module module new_contributor Help guide this first time contributor plugins plugin (any type) tests tests labels Feb 17, 2025
@felixfontein felixfontein added check-before-release PR will be looked at again shortly before release and merged if possible. backport-10 Automatically create a backport for the stable-10 branch labels Feb 17, 2025
@NomakCooper
Copy link
Contributor Author

I have corrected the result object because previously it was added directly to the register.
This merged the changed and failed fields with the result, potentially causing misunderstandings or incorrect handling. Now, the module returns the result object within a units object inside the register.

like:

results.units['sshd-keygen.target']

Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @NomakCooper
Thanks for your PR! I've made an initial review.

@NomakCooper
Copy link
Contributor Author

NomakCooper commented Feb 19, 2025

@russoz

Thanks for your review comments.

I've made some changes based on your suggestions:

  • I improved the syntax of the condition in the integration test as you recommended.

    when: >
    (ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] and ansible_distribution_major_version is version('7', '>=')) or
    ansible_distribution == 'Fedora' or
    (ansible_distribution == 'Ubuntu' and ansible_distribution_version is version('15.04', '>=')) or
    (ansible_distribution == 'Debian' and ansible_distribution_version is version('8', '>=')) or
    ansible_os_family == 'Suse'

  • Regarding the module's failure conditions, you were right, two of them were unnecessary. Since I’m still new to writing Ansible modules, I tend to prefer failure over returning an empty result.

  • I removed is_systemd and instead introduced a simple systemctl --version check within a try, failing if the command execution encounters an error.

    systemctl_bin = module.get_bin_path('systemctl', required=True)
    try:
    run_command(module, [systemctl_bin, '--version'])
    except Exception as e:
    module.fail_json(msg="Failed to run systemctl: {0}".format(str(e)))

  • I also removed the if condition on unitname (previously lines 270-271).

  • I renamed add_properties to extract_unit_properties. Additionally, fact is no longer passed to the function; instead, as you suggested, it is updated using fact.update.

    fact.update(extract_unit_properties(unit_data, full_props))

    fact.update(extract_unit_properties(unit_data, minimal_keys))

    fact.update(extract_unit_properties(unit_data, full_props))

    def extract_unit_properties(unit_data, prop_list):
    extracted = {}
    for prop in prop_list:
    key = prop.lower()
    if key in unit_data:
    extracted[key] = unit_data[key]
    return extracted

@NomakCooper NomakCooper requested a review from russoz February 19, 2025 03:56
Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @NomakCooper thanks for the adjustments. I have a couple of additional comments, but I re iewed from the phone so I may have easily made some mistake. Take it with a grain of salt.

@ansibullbot ansibullbot added the docs_fragments docs_fragments plugin (shared docs) label Feb 19, 2025
@NomakCooper
Copy link
Contributor Author

NomakCooper commented Feb 19, 2025

@russoz

I have made some changes based on the suggestions.

  • Corrected the syntax in DOCUMENTATION.

    - Even if a unit has a V(loadstate) of V(not-found) or V(masked), it is returned,

  • Added a new line in the unitname description.

    options:
    unitname:
    description:
    - List of unit names to process.
    - It supports .service, .target, .socket, and .mount units type.

  • Added extends_documentation_fragment and created the new plugins/doc_fragments/systemd.py with additional notes that handle the basic options and properties.

    extends_documentation_fragment:
    - community.general.systemd.documentation
    - community.general.attributes
    - community.general.attributes.info_module

    unitname:
    description:
    - List of unit names to process.
    - It supports .service, .target, .socket, and .mount units type.
    - Each name must correspond to the full name of the systemd unit.

    notes:
    - Regardless of whether the property is default or extra, it will be output in lowercase once extracted.
    - Default properties depend on the type of systemd unit.
    - service properties V(name), V(loadstate), V(activestate), V(substate), V(fragmentpath), V(unitfilepreset), V(unitfilestate), V(mainpid), V(execmainpid).
    - target properties V(name), V(loadstate), V(activestate), V(substate), V(fragmentpath), V(unitfilepreset), V(unitfilestate).
    - socket properties V(name), V(loadstate), V(activestate), V(substate), V(fragmentpath), V(unitfilepreset), V(unitfilestate).
    - mount properties V(name), V(loadstate), V(activestate), V(substate), V(what), V(where), V(type), V(options).

  • Modified the run_command function to use use_unsafe_shell and check_rc.

    def run_command(module, cmd):
    rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True, check_rc=True)
    return stdout.strip()

    According to the Ansible documentation, using use_unsafe_shell is mandatory with module.run_command.

  • Modified the extract_unit_properties function.

    def extract_unit_properties(unit_data, prop_list):
    lowerprop = [x.lower() for x in prop_list]
    extracted = {
    prop: unit_data[prop] for prop in lowerprop if prop in unit_data
    }
    return extracted

@ansibullbot ansibullbot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR and removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR labels Feb 19, 2025
@felixfontein
Copy link
Collaborator

  • Modified the run_command function to use use_unsafe_shell and check_rc.
    def run_command(module, cmd):
    rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True, check_rc=True)
    return stdout.strip()

I think you misunderstood that documenation. You only need it if you really need a shell. In 99% of all cases you don't need a shell. Using a shell is quite dangerous in general, so if there is no absolute need to use it, it should not be used.

Regarding the systemd docs fragment: I don't think that makes sense here.

@NomakCooper
Copy link
Contributor Author

@felixfontein @russoz Thanks for the help and suggestions.

Regarding use_unsafe_shell, I had misunderstood the documentation.

Latest changes:

  • Removed use_unsafe_shell=True
  • Removed documentation plugins/doc_fragments/systemd.py
  • Added a new line to the description of unitname
    unitname:
    description:
    - List of unit names to process.
    - It supports .service, .target, .socket, and .mount units type.
    - Each name must correspond to the full name of the C(systemd) unit.

@NomakCooper
Copy link
Contributor Author

  • Corrected the documentation syntax.
  • Changes the functions to use the systemctl show command with -- characters by default.
  • Updated the return documentation with the specifications of all the base properties.
    units:
    description: Dictionary of systemd unit info keyed by unit name.
    returned: success
    type: dict
    elements: dict
    contains:
    name:
    description: Unit full name.
    returned: always
    type: str
    sample: systemd-journald.service
    loadstate:
    description:
    - The state of the unit's configuration load.
    - Either V(loaded), V(not-found), V(masked).
    returned: always
    type: str
    sample: loaded
    activestate:
    description:
    - The current active state of the unit.
    - Either V(active), V(inactive), V(failed).
    returned: always
    type: str
    sample: active
    substate:
    description:
    - The detailed sub state of the unit.
    - Either V(running), V(dead), V(exited), V(failed), V(listening), V(active), V(mounted).
    returned: always
    type: str
    sample: running
    fragmentpath:
    description: Path to the unit's fragment file.
    returned: always except for C(.mount) units.
    type: str
    sample: /usr/lib/systemd/system/systemd-journald.service
    unitfilepreset:
    description:
    - The preset configuration state for the unit file.
    - Either V(enabled), V(disabled).
    returned: always except for C(.mount) units.
    type: str
    sample: disabled
    unitfilestate:
    description:
    - The actual configuration state for the unit file.
    - Either V(enabled), V(disabled).
    returned: always except for C(.mount) units.
    type: str
    sample: enabled
    mainpid:
    description: PID of the main process of the unit.
    returned: only for C(.service) units.
    type: str
    sample: 798
    execmainpid:
    description: PID of the ExecStart process of the unit.
    returned: only for C(.service) units.
    type: str
    sample: 799
    options:
    description: The mount options.
    returned: only for C(.mount) units.
    type: str
    sample: rw,relatime,noquota
    type:
    description: The filesystem type of the mounted device.
    returned: only for C(.mount) units.
    type: str
    sample: ext4
    what:
    description: The device that is mounted.
    returned: only for C(.mount) units.
    type: str
    sample: /dev/sda1
    where:
    description: The mount point where the device is mounted.
    returned: only for C(.mount) units.
    type: str
    sample: /

@NomakCooper
Copy link
Contributor Author

@felixfontein @russoz

Do you have any other suggestions, or does it look good to you? 😊

@felixfontein
Copy link
Collaborator

I have some more docs suggestions:

diff --git a/plugins/modules/systemd_info.py b/plugins/modules/systemd_info.py
index f550b813d..a5e03a228 100644
--- a/plugins/modules/systemd_info.py
+++ b/plugins/modules/systemd_info.py
@@ -16,8 +16,8 @@ description:
   - This module gathers info about systemd units (services, targets, sockets, mount).
   - It runs C(systemctl list-units) (or processes selected units) and collects properties
     for each unit using C(systemctl show).
-  - Even if a unit has a V(loadstate) of V(not-found) or V(masked), it is returned,
-    but only with the minimal properties (V(name), V(loadstate), V(activestate), V(substate)).
+  - Even if a unit has a RV(units[].loadstate) of V(not-found) or V(masked), it is returned,
+    but only with the minimal properties (RV(units[].name), RV(units[].loadstate), RV(units[].activestate), RV(units[].substate)).
   - When O(unitname) and O(extra_properties) are used, the module first checks if the unit exists,
     then check if properties exist. If not, the module fails.
 version_added: "10.4.0"
@@ -66,7 +66,9 @@ EXAMPLES = r'''
 
 RETURN = r'''
 units:
-  description: Dictionary of systemd unit info keyed by unit name.
+  description:
+    - Dictionary of systemd unit info keyed by unit name.
+    - Additional fields will be returned depending on the value of O(extra_properties).
   returned: success
   type: dict
   elements: dict
@@ -79,24 +81,37 @@ units:
     loadstate:
       description:
         - The state of the unit's configuration load.
-        - Either V(loaded), V(not-found), V(masked).
       returned: always
       type: str
       sample: loaded
+      choices:
+        - loaded
+        - not-found
+        - masked
     activestate:
       description:
         - The current active state of the unit.
-        - Either V(active), V(inactive), V(failed).
       returned: always
       type: str
       sample: active
+      choices:
+        - active
+        - inactive
+        - failed
     substate:
       description:
         - The detailed sub state of the unit.
-        - Either V(running), V(dead), V(exited), V(failed), V(listening), V(active), V(mounted).
       returned: always
       type: str
       sample: running
+      choices:
+        - running
+        - dead
+        - exited
+        - failed
+        - listening
+        - active
+        - mounted
     fragmentpath:
       description: Path to the unit's fragment file.
       returned: always except for C(.mount) units.
@@ -105,17 +120,21 @@ units:
     unitfilepreset:
       description:
         - The preset configuration state for the unit file.
-        - Either V(enabled), V(disabled).
       returned: always except for C(.mount) units.
       type: str
       sample: disabled
+      choices:
+        - enabled
+        - disabled
     unitfilestate:
       description:
         - The actual configuration state for the unit file.
-        - Either V(enabled), V(disabled).
       returned: always except for C(.mount) units.
       type: str
       sample: enabled
+      choices:
+        - enabled
+        - disabled
     mainpid:
       description: PID of the main process of the unit.
       returned: only for C(.service) units.

Besides that, I think it's good.

@NomakCooper
Copy link
Contributor Author

Thanks @felixfontein

  • For the documentation changes, I wanted to use RV(...), but I wasn’t sure about the correct syntax to use.

  • Regarding the basic properties descriptions, I didn’t know that choices could be used. I thought it was only for documenting options. Additionally, the values I described are not the only possible values that exist in systemd.

For example, ActiveState could also be activating, deactivating, maintenance, reloading, or refreshing.

I included only the most common ones in the documentation since knowing all the possible values is quite difficult.

@ansibullbot ansibullbot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR and removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR labels Feb 22, 2025
@NomakCooper
Copy link
Contributor Author

@felixfontein

  • Updated the descriptions as you suggested
  • Removed the try blocks for get_bin_path and run_command

Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@felixfontein felixfontein merged commit 8425464 into ansible-collections:main Feb 23, 2025
138 checks passed
Copy link

patchback bot commented Feb 23, 2025

Backport to stable-10: 💚 backport PR created

✅ Backport PR branch: patchback/backports/stable-10/8425464c0a8ab1e017c3cd18286e7db742efb3ba/pr-9764

Backported as #9800

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@felixfontein felixfontein removed the check-before-release PR will be looked at again shortly before release and merged if possible. label Feb 23, 2025
patchback bot pushed a commit that referenced this pull request Feb 23, 2025
* add systemd_info module

* fix object results

* apply review changes

* apply module change and add doc_fragments

* removed use_unsafe_shell and doc_fragments/systemd

* fix unitname description doc

* fixed doc, replaced systemctl show syntax, added base prop result doc

* fix documentation

* fix RV values in description

* fix RV() description values

* add get_bin_path try/fail and remove list()

* fix doc, removed try block

* add Archlinux in integration test

(cherry picked from commit 8425464)
@felixfontein
Copy link
Collaborator

@NomakCooper thanks for your contribution!
@russoz thanks for reviewing!

felixfontein pushed a commit that referenced this pull request Feb 23, 2025
…9800)

Add new systemd_info module (#9764)

* add systemd_info module

* fix object results

* apply review changes

* apply module change and add doc_fragments

* removed use_unsafe_shell and doc_fragments/systemd

* fix unitname description doc

* fixed doc, replaced systemctl show syntax, added base prop result doc

* fix documentation

* fix RV values in description

* fix RV() description values

* add get_bin_path try/fail and remove list()

* fix doc, removed try block

* add Archlinux in integration test

(cherry picked from commit 8425464)

Co-authored-by: Nocchia <133043574+NomakCooper@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-10 Automatically create a backport for the stable-10 branch docs_fragments docs_fragments plugin (shared docs) integration tests/integration module module new_contributor Help guide this first time contributor plugins plugin (any type) tests tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants