mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-25 05:23:58 -07:00 
			
		
		
		
	Support for simpleinit-msb init system (#6618)
* Support for simpleinit-msb init system
* Drop unused imports
* Correct regex
* Fix documentation
* Address BOTMETA
* PEP8 compliance
* Drop irrelevant snippet
* Add missing option type in docs
* PEP8 compliance
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Daemonize commands in service control to handle telinit broken behavior
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Unify examples section
* Add unit tests for service state detection
* Drop unused import
* Add service enable/disable tests
* Test get_service_tools()
* Do not shadow fail_json()
* Reuse module init
* Implement service_enabled() and associated tests
* Update plugins/modules/simpleinit_msb.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Indent
* Bump version_added
* Bump requirements
* Reword and move to notes
---------
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 8dc5a60294)
Co-authored-by: Val V <vaygr@users.noreply.github.com>
		
	
			
		
			
				
	
	
		
			322 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # Copyright (c) 2016-2023, Vlad Glagolev <scm@vaygr.net>
 | |
| #
 | |
| # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function
 | |
| 
 | |
| __metaclass__ = type
 | |
| 
 | |
| 
 | |
| DOCUMENTATION = '''
 | |
| ---
 | |
| module: simpleinit_msb
 | |
| short_description: Manage services on Source Mage GNU/Linux
 | |
| version_added: 7.5.0
 | |
| description:
 | |
|   - Controls services on remote hosts using C(simpleinit-msb).
 | |
| notes:
 | |
|   - This module needs ansible-core 2.15.5 or newer. Older versions have a broken and insufficient daemonize functionality.
 | |
| author: "Vlad Glagolev (@vaygr)"
 | |
| extends_documentation_fragment:
 | |
|   - community.general.attributes
 | |
| attributes:
 | |
|   check_mode:
 | |
|     support: full
 | |
|   diff_mode:
 | |
|     support: none
 | |
| options:
 | |
|   name:
 | |
|     type: str
 | |
|     description:
 | |
|       - Name of the service.
 | |
|     required: true
 | |
|     aliases: ['service']
 | |
|   state:
 | |
|     type: str
 | |
|     required: false
 | |
|     choices: [ running, started, stopped, restarted, reloaded ]
 | |
|     description:
 | |
|       - V(started)/V(stopped) are idempotent actions that will not run
 | |
|         commands unless necessary.  V(restarted) will always bounce the
 | |
|         service.  V(reloaded) will always reload.
 | |
|       - At least one of O(state) and O(enabled) are required.
 | |
|       - Note that V(reloaded) will start the
 | |
|         service if it is not already started, even if your chosen init
 | |
|         system would not normally.
 | |
|   enabled:
 | |
|     type: bool
 | |
|     required: false
 | |
|     description:
 | |
|       - Whether the service should start on boot.
 | |
|       - At least one of O(state) and O(enabled) are required.
 | |
| '''
 | |
| 
 | |
| EXAMPLES = '''
 | |
| - name: Example action to start service httpd, if not running
 | |
|   community.general.simpleinit_msb:
 | |
|     name: httpd
 | |
|     state: started
 | |
| 
 | |
| - name: Example action to stop service httpd, if running
 | |
|   community.general.simpleinit_msb:
 | |
|     name: httpd
 | |
|     state: stopped
 | |
| 
 | |
| - name: Example action to restart service httpd, in all cases
 | |
|   community.general.simpleinit_msb:
 | |
|     name: httpd
 | |
|     state: restarted
 | |
| 
 | |
| - name: Example action to reload service httpd, in all cases
 | |
|   community.general.simpleinit_msb:
 | |
|     name: httpd
 | |
|     state: reloaded
 | |
| 
 | |
| - name: Example action to enable service httpd, and not touch the running state
 | |
|   community.general.simpleinit_msb:
 | |
|     name: httpd
 | |
|     enabled: true
 | |
| '''
 | |
| 
 | |
| import os
 | |
| import re
 | |
| 
 | |
| from ansible.module_utils.basic import AnsibleModule
 | |
| from ansible.module_utils.service import daemonize
 | |
| 
 | |
| 
 | |
| class SimpleinitMSB(object):
 | |
|     """
 | |
|     Main simpleinit-msb service manipulation class
 | |
|     """
 | |
| 
 | |
|     def __init__(self, module):
 | |
|         self.module = module
 | |
|         self.name = module.params['name']
 | |
|         self.state = module.params['state']
 | |
|         self.enable = module.params['enabled']
 | |
|         self.changed = False
 | |
|         self.running = None
 | |
|         self.action = None
 | |
|         self.telinit_cmd = None
 | |
|         self.svc_change = False
 | |
| 
 | |
|     def execute_command(self, cmd, daemon=False):
 | |
|         if not daemon:
 | |
|             return self.module.run_command(cmd)
 | |
|         else:
 | |
|             return daemonize(self.module, cmd)
 | |
| 
 | |
|     def check_service_changed(self):
 | |
|         if self.state and self.running is None:
 | |
|             self.module.fail_json(msg="failed determining service state, possible typo of service name?")
 | |
|         # Find out if state has changed
 | |
|         if not self.running and self.state in ["started", "running", "reloaded"]:
 | |
|             self.svc_change = True
 | |
|         elif self.running and self.state in ["stopped", "reloaded"]:
 | |
|             self.svc_change = True
 | |
|         elif self.state == "restarted":
 | |
|             self.svc_change = True
 | |
|         if self.module.check_mode and self.svc_change:
 | |
|             self.module.exit_json(changed=True, msg='service state changed')
 | |
| 
 | |
|     def modify_service_state(self):
 | |
|         # Only do something if state will change
 | |
|         if self.svc_change:
 | |
|             # Control service
 | |
|             if self.state in ['started', 'running']:
 | |
|                 self.action = "start"
 | |
|             elif not self.running and self.state == 'reloaded':
 | |
|                 self.action = "start"
 | |
|             elif self.state == 'stopped':
 | |
|                 self.action = "stop"
 | |
|             elif self.state == 'reloaded':
 | |
|                 self.action = "reload"
 | |
|             elif self.state == 'restarted':
 | |
|                 self.action = "restart"
 | |
| 
 | |
|             if self.module.check_mode:
 | |
|                 self.module.exit_json(changed=True, msg='changing service state')
 | |
| 
 | |
|             return self.service_control()
 | |
|         else:
 | |
|             # If nothing needs to change just say all is well
 | |
|             rc = 0
 | |
|             err = ''
 | |
|             out = ''
 | |
|             return rc, out, err
 | |
| 
 | |
|     def get_service_tools(self):
 | |
|         paths = ['/sbin', '/usr/sbin', '/bin', '/usr/bin']
 | |
|         binaries = ['telinit']
 | |
|         location = dict()
 | |
| 
 | |
|         for binary in binaries:
 | |
|             location[binary] = self.module.get_bin_path(binary, opt_dirs=paths)
 | |
| 
 | |
|         if location.get('telinit', False) and os.path.exists("/etc/init.d/smgl_init"):
 | |
|             self.telinit_cmd = location['telinit']
 | |
| 
 | |
|         if self.telinit_cmd is None:
 | |
|             self.module.fail_json(msg='cannot find telinit script for simpleinit-msb, aborting...')
 | |
| 
 | |
|     def get_service_status(self):
 | |
|         self.action = "status"
 | |
|         rc, status_stdout, status_stderr = self.service_control()
 | |
| 
 | |
|         if self.running is None and status_stdout.count('\n') <= 1:
 | |
|             cleanout = status_stdout.lower().replace(self.name.lower(), '')
 | |
| 
 | |
|             if "is not running" in cleanout:
 | |
|                 self.running = False
 | |
|             elif "is running" in cleanout:
 | |
|                 self.running = True
 | |
| 
 | |
|         return self.running
 | |
| 
 | |
|     def service_enable(self):
 | |
|         # Check if the service is already enabled/disabled
 | |
|         if not self.enable ^ self.service_enabled():
 | |
|             return
 | |
| 
 | |
|         action = "boot" + ("enable" if self.enable else "disable")
 | |
| 
 | |
|         (rc, out, err) = self.execute_command("%s %s %s" % (self.telinit_cmd, action, self.name))
 | |
| 
 | |
|         self.changed = True
 | |
| 
 | |
|         for line in err.splitlines():
 | |
|             if self.enable and line.find('already enabled') != -1:
 | |
|                 self.changed = False
 | |
|                 break
 | |
|             if not self.enable and line.find('already disabled') != -1:
 | |
|                 self.changed = False
 | |
|                 break
 | |
| 
 | |
|         if not self.changed:
 | |
|             return
 | |
| 
 | |
|         return (rc, out, err)
 | |
| 
 | |
|     def service_enabled(self):
 | |
|         self.service_exists()
 | |
| 
 | |
|         (rc, out, err) = self.execute_command("%s %sd" % (self.telinit_cmd, self.enable))
 | |
| 
 | |
|         service_enabled = False if self.enable else True
 | |
| 
 | |
|         rex = re.compile(r'^%s$' % self.name)
 | |
| 
 | |
|         for line in out.splitlines():
 | |
|             if rex.match(line):
 | |
|                 service_enabled = True if self.enable else False
 | |
|                 break
 | |
| 
 | |
|         return service_enabled
 | |
| 
 | |
|     def service_exists(self):
 | |
|         (rc, out, err) = self.execute_command("%s list" % self.telinit_cmd)
 | |
| 
 | |
|         service_exists = False
 | |
| 
 | |
|         rex = re.compile(r'^\w+\s+%s$' % self.name)
 | |
| 
 | |
|         for line in out.splitlines():
 | |
|             if rex.match(line):
 | |
|                 service_exists = True
 | |
|                 break
 | |
| 
 | |
|         if not service_exists:
 | |
|             self.module.fail_json(msg='telinit could not find the requested service: %s' % self.name)
 | |
| 
 | |
|     def service_control(self):
 | |
|         self.service_exists()
 | |
| 
 | |
|         svc_cmd = "%s run %s" % (self.telinit_cmd, self.name)
 | |
| 
 | |
|         rc_state, stdout, stderr = self.execute_command("%s %s" % (svc_cmd, self.action), daemon=True)
 | |
| 
 | |
|         return (rc_state, stdout, stderr)
 | |
| 
 | |
| 
 | |
| def build_module():
 | |
|     return AnsibleModule(
 | |
|         argument_spec=dict(
 | |
|             name=dict(required=True, aliases=['service']),
 | |
|             state=dict(choices=['running', 'started', 'stopped', 'restarted', 'reloaded']),
 | |
|             enabled=dict(type='bool'),
 | |
|         ),
 | |
|         supports_check_mode=True,
 | |
|         required_one_of=[['state', 'enabled']],
 | |
|     )
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     module = build_module()
 | |
| 
 | |
|     service = SimpleinitMSB(module)
 | |
| 
 | |
|     rc = 0
 | |
|     out = ''
 | |
|     err = ''
 | |
|     result = {}
 | |
|     result['name'] = service.name
 | |
| 
 | |
|     # Find service management tools
 | |
|     service.get_service_tools()
 | |
| 
 | |
|     # Enable/disable service startup at boot if requested
 | |
|     if service.module.params['enabled'] is not None:
 | |
|         service.service_enable()
 | |
|         result['enabled'] = service.enable
 | |
| 
 | |
|     if module.params['state'] is None:
 | |
|         # Not changing the running state, so bail out now.
 | |
|         result['changed'] = service.changed
 | |
|         module.exit_json(**result)
 | |
| 
 | |
|     result['state'] = service.state
 | |
| 
 | |
|     service.get_service_status()
 | |
| 
 | |
|     # Calculate if request will change service state
 | |
|     service.check_service_changed()
 | |
| 
 | |
|     # Modify service state if necessary
 | |
|     (rc, out, err) = service.modify_service_state()
 | |
| 
 | |
|     if rc != 0:
 | |
|         if err:
 | |
|             module.fail_json(msg=err)
 | |
|         else:
 | |
|             module.fail_json(msg=out)
 | |
| 
 | |
|     result['changed'] = service.changed | service.svc_change
 | |
|     if service.module.params['enabled'] is not None:
 | |
|         result['enabled'] = service.module.params['enabled']
 | |
| 
 | |
|     if not service.module.params['state']:
 | |
|         status = service.get_service_status()
 | |
|         if status is None:
 | |
|             result['state'] = 'absent'
 | |
|         elif status is False:
 | |
|             result['state'] = 'started'
 | |
|         else:
 | |
|             result['state'] = 'stopped'
 | |
|     else:
 | |
|         # as we may have just bounced the service the service command may not
 | |
|         # report accurate state at this moment so just show what we ran
 | |
|         if service.module.params['state'] in ['started', 'restarted', 'running', 'reloaded']:
 | |
|             result['state'] = 'started'
 | |
|         else:
 | |
|             result['state'] = 'stopped'
 | |
| 
 | |
|     module.exit_json(**result)
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |