diff --git a/changelogs/fragments/reboot-add-search-paths-parameter.yaml b/changelogs/fragments/reboot-add-search-paths-parameter.yaml new file mode 100644 index 0000000000..aab697c6dc --- /dev/null +++ b/changelogs/fragments/reboot-add-search-paths-parameter.yaml @@ -0,0 +1,2 @@ +minor_changes: + - 'reboot - add parameter for specifying paths to search for the ``shutdown`` command (https://github.com/ansible/ansible/issues/51190)' diff --git a/lib/ansible/modules/system/reboot.py b/lib/ansible/modules/system/reboot.py index 9e45ed4003..c22969678a 100644 --- a/lib/ansible/modules/system/reboot.py +++ b/lib/ansible/modules/system/reboot.py @@ -13,6 +13,9 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', DOCUMENTATION = r''' module: reboot short_description: Reboot a machine +notes: + - C(PATH) is ignored on the remote node when searching for the C(shutdown) command. Use C(search_paths) + to specify locations to search if the default paths do not work. description: - Reboot a machine, wait for it to go down, come back up, and respond to commands. - For Windows targets, use the M(win_reboot) module instead. @@ -54,6 +57,14 @@ options: - Message to display to users before reboot. type: str default: Reboot initiated by Ansible + + search_paths: + description: + - Paths to search on the remote machine for the C(shutdown) command. + - I(Only) these paths will be searched for the C(shutdown) command. C(PATH) is ignored in the remote node when searching for the C(shutdown) command. + type: list + default: ['/sbin', '/usr/sbin', '/usr/local/sbin'] + version_added: '2.8' seealso: - module: win_reboot author: diff --git a/lib/ansible/plugins/action/reboot.py b/lib/ansible/plugins/action/reboot.py index 276ddf3068..2d829fad76 100644 --- a/lib/ansible/plugins/action/reboot.py +++ b/lib/ansible/plugins/action/reboot.py @@ -12,6 +12,7 @@ from datetime import datetime, timedelta from ansible.errors import AnsibleError, AnsibleConnectionFailure from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.collections import is_string from ansible.plugins.action import ActionBase from ansible.utils.display import Display @@ -24,7 +25,7 @@ class TimedOutException(Exception): class ActionModule(ActionBase): TRANSFERS_FILES = False - _VALID_ARGS = frozenset(('connect_timeout', 'msg', 'post_reboot_delay', 'pre_reboot_delay', 'test_command', 'reboot_timeout')) + _VALID_ARGS = frozenset(('connect_timeout', 'msg', 'post_reboot_delay', 'pre_reboot_delay', 'test_command', 'reboot_timeout', 'search_paths')) DEFAULT_REBOOT_TIMEOUT = 600 DEFAULT_CONNECT_TIMEOUT = None @@ -130,13 +131,32 @@ class ActionModule(ActionBase): def get_shutdown_command(self, task_vars, distribution): shutdown_bin = self._get_value_from_facts('SHUTDOWN_COMMANDS', distribution, 'DEFAULT_SHUTDOWN_COMMAND') + default_search_paths = ['/sbin', '/usr/sbin', '/usr/local/sbin'] + search_paths = self._task.args.get('search_paths', default_search_paths) - display.debug('{action}: running find module to get path for "{command}"'.format(action=self._task.action, command=shutdown_bin)) + # FIXME: switch all this to user arg spec validation methods when they are available + # Convert bare strings to a list + if is_string(search_paths): + search_paths = [search_paths] + + # Error if we didn't get a list + err_msg = "'search_paths' must be a string or flat list of strings, got {0}" + try: + incorrect_type = any(not is_string(x) for x in search_paths) + except TypeError as te: + raise AnsibleError(err_msg.format(search_paths)) + if not isinstance(search_paths, list) or incorrect_type: + raise AnsibleError(err_msg.format(search_paths)) + + display.debug('{action}: running find module looking in {paths} to get path for "{command}"'.format( + action=self._task.action, + command=shutdown_bin, + paths=search_paths)) find_result = self._execute_module( task_vars=task_vars, module_name='find', module_args={ - 'paths': ['/sbin', '/usr/sbin', '/usr/local/sbin'], + 'paths': search_paths, 'patterns': [shutdown_bin], 'file_type': 'any' } @@ -144,7 +164,7 @@ class ActionModule(ActionBase): full_path = [x['path'] for x in find_result['files']] if not full_path: - raise AnsibleError('Unable to find command "{0}" in system paths.'.format(shutdown_bin)) + raise AnsibleError('Unable to find command "{0}" in search paths: {1}'.format(shutdown_bin, search_paths)) self._shutdown_command = full_path[0] return self._shutdown_command diff --git a/test/integration/targets/reboot/tasks/main.yml b/test/integration/targets/reboot/tasks/main.yml index 7f7d1bb72e..2568b9b290 100644 --- a/test/integration/targets/reboot/tasks/main.yml +++ b/test/integration/targets/reboot/tasks/main.yml @@ -37,11 +37,13 @@ - name: Reboot with all options reboot: connect_timeout: 30 + search_paths: /usr/local/bin msg: Rebooting post_reboot_delay: 1 pre_reboot_delay: 61 test_command: uptime reboot_timeout: 500 + register: reboot_result - import_tasks: check_reboot.yml @@ -51,6 +53,7 @@ reboot: post_reboot_delay: -0.5 pre_reboot_delay: -61 + register: reboot_result - import_tasks: check_reboot.yml @@ -75,6 +78,27 @@ vars: timeout: "{{ timeout_value[ansible_facts['distribution'] | lower] | default(60) }}" + - name: Test molly-guard + block: + - import_tasks: get_boot_time.yml + + - name: Install molly-guard + apt: + update_cache: yes + name: molly-guard + state: present + + - name: Reboot when molly-guard is installed + reboot: + search_paths: /lib/molly-guard + register: reboot_result + + - import_tasks: check_reboot.yml + + when: ansible_facts.distribution in ['Debian', 'Ubuntu'] + tags: + - molly-guard + always: - name: Cleanup temp file file: