Last active
September 27, 2024 01:32
-
-
Save samdoran/eb19f126acaa620133babca09106a2ed to your computer and use it in GitHub Desktop.
Ansible module to set systemd default target
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
# Copyright (c) 2020 Sam Doran | |
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | |
from __future__ import absolute_import, division, print_function | |
__metaclass__ = type | |
DOCUMENTATION = """ | |
--- | |
module: systemd_default | |
author: | |
- Sam Doran (@samdoran) | |
short_description: Configure the systemd default target | |
notes: [] | |
description: | |
- Set the systemd default target and optionally apply it immediately. | |
options: | |
target: | |
description: | |
- Name of the target. Must be a valid target unit file on the remote system. | |
The '.target' file extension is optional. If a file extension is given, it must be '.target'. | |
type: str | |
required: yes | |
apply: | |
description: Whether or not to switch to the C(target) immediately. | |
type: bool | |
default: no | |
required: no | |
""" | |
EXAMPLES = """ | |
- name: Set default target to graphical and switch to it immediately | |
systemd_default: | |
target: graphical.target | |
apply: yes | |
- name: Set default target to multi-user | |
systemd_default: | |
target: multi-user | |
""" | |
RETURN = """ | |
previous_target: | |
description: Name of the default target before the module made changes | |
returned: always | |
type: string | |
sample: multi-user.target | |
target: | |
description: Name of the desired target. | |
returned: always | |
type: string | |
sample: graphical.target | |
""" | |
from ansible.module_utils.basic import AnsibleModule | |
from ansible.module_utils.common.process import get_bin_path | |
def normalize_target_name(module, target): | |
# Sanity check the file extension if one was provided | |
if len(target.split('.')) >= 2: | |
ext = target.split('.')[-1] | |
if ext != 'target': | |
module.fail_json("Invalid extension '{0}' on '{1}'".format(ext, target)) | |
if target.split('.')[-1] != 'target': | |
return '{0}.target'.format(target) | |
return target | |
def list_targets(module, systemd_path): | |
cmd = [systemd_path, 'list-units', '--type', 'target', '--no-legend', '--no-block', '--no-pager', '--all'] | |
rc, out, err = module.run_command(cmd) | |
if rc != 0: | |
module.fail_json("Unable to list available targets") | |
valid_targets = set() | |
for line in out.splitlines(): | |
line = ' '.join(line.split(maxsplit=4)) | |
unit, load, active, sub, description = line.split(maxsplit=4) | |
valid_targets.add(unit) | |
return valid_targets | |
def validate_target(module, target, systemd_path): | |
valid_targets = list_targets(module, systemd_path) | |
if target not in valid_targets: | |
module.fail_json("The specified target '{0}' is not a valid target on this system".format(target)) | |
def get_current_default(module, systemd_path): | |
cmd = [systemd_path, 'get-default'] | |
rc, out, err = module.run_command(cmd) | |
if rc != 0: | |
module.fail_json(msg='Unable to get current default target') | |
return out.rstrip('\n\r') | |
def set_default_target(module, target, systemd_path): | |
current_default = get_current_default(module, systemd_path) | |
changed = False | |
if current_default != target: | |
changed = True | |
if not module.check_mode: | |
set_cmd = [systemd_path, 'set-default', target] | |
rc, out, err = module.run_command(set_cmd) | |
if rc != 0: | |
module.fail_json("Failed to set default target to '{0}'".format(target)) | |
return changed, current_default | |
def get_active_state(module, target, systemd_path): | |
status_cmd = [systemd_path, 'is-active', target] | |
rc, out, err = module.run_command(status_cmd) | |
return out.rstrip('\n\r') | |
def apply_target(module, target, systemd_path): | |
applied = False | |
current_state = get_active_state(module, target, systemd_path) | |
if current_state == 'active': | |
return applied | |
isolate_cmd = [systemd_path, 'isolate', target] | |
rc, out, err = module.run_command(isolate_cmd) | |
if rc != 0: | |
module.fail_json("Failed to apply target '{0}'".format(target)) | |
applied = True | |
return applied | |
def main(): | |
module = AnsibleModule( | |
argument_spec={ | |
'target': {'type': 'str', 'required': True}, | |
'apply': {'type': 'bool', 'default': False}, | |
}, | |
supports_check_mode=True, | |
) | |
try: | |
systemd_path = get_bin_path('systemctl') | |
except ValueError: | |
module.fail_json(msg="Unable to find 'systemctl'") | |
full_target_name = normalize_target_name(module, module.params['target']) | |
validate_target(module, full_target_name, systemd_path) | |
changed, current = set_default_target(module, full_target_name, systemd_path) | |
applied = False | |
if module.params['apply']: | |
applied = apply_target(module, full_target_name, systemd_path) | |
results = { | |
'changed': changed or applied, | |
'previous_target': current, | |
'target': full_target_name, | |
} | |
module.exit_json(**results) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A gentleman and a scholar, thank you!