1""" 2Service support for RHEL-based systems, including support for both upstart and sysvinit 3 4.. important:: 5 If you feel that Salt should be using this module to manage services on a 6 minion, and it is using a different module (or gives an error similar to 7 *'service.start' is not available*), see :ref:`here 8 <module-provider-override>`. 9""" 10 11import fnmatch 12import glob 13import logging 14import os 15import re 16import stat 17 18import salt.utils.path 19 20log = logging.getLogger(__name__) 21 22__func_alias__ = {"reload_": "reload"} 23 24# Define the module's virtual name 25__virtualname__ = "service" 26 27# Import upstart module if needed 28HAS_UPSTART = False 29if salt.utils.path.which("initctl"): 30 try: 31 # Don't re-invent the wheel, import the helper functions from the 32 # upstart module. 33 from salt.modules.upstart_service import ( 34 _upstart_enable, 35 _upstart_disable, 36 _upstart_is_enabled, 37 ) 38 except Exception as exc: # pylint: disable=broad-except 39 log.error( 40 "Unable to import helper functions from salt.modules.upstart: %s", exc 41 ) 42 else: 43 HAS_UPSTART = True 44 45 46def __virtual__(): 47 """ 48 Only work on select distros which still use Red Hat's /usr/bin/service for 49 management of either sysvinit or a hybrid sysvinit/upstart init system. 50 """ 51 # Disable when booted with systemd 52 if __utils__["systemd.booted"](__context__): 53 return ( 54 False, 55 "The rh_service execution module failed to load: this system was booted" 56 " with systemd.", 57 ) 58 59 # Enable on these platforms only. 60 enable = { 61 "XenServer", 62 "XCP-ng", 63 "RedHat", 64 "CentOS", 65 "ScientificLinux", 66 "CloudLinux", 67 "Amazon", 68 "Fedora", 69 "ALT", 70 "OEL", 71 "SUSE Enterprise Server", 72 "SUSE", 73 "McAfee OS Server", 74 "VirtuozzoLinux", 75 } 76 if __grains__["os"] in enable: 77 78 if __grains__["os"] == "SUSE": 79 if str(__grains__["osrelease"]).startswith("11"): 80 return __virtualname__ 81 else: 82 return (False, "Cannot load rh_service module on SUSE > 11") 83 84 osrelease_major = __grains__.get("osrelease_info", [0])[0] 85 86 if __grains__["os"] in ("XenServer", "XCP-ng"): 87 if osrelease_major >= 7: 88 return ( 89 False, 90 "XenServer and XCP-ng >= 7 use systemd, will not load rh_service.py" 91 " as virtual 'service'", 92 ) 93 return __virtualname__ 94 95 if __grains__["os"] == "Fedora": 96 if osrelease_major >= 15: 97 return ( 98 False, 99 "Fedora >= 15 uses systemd, will not load rh_service.py " 100 "as virtual 'service'", 101 ) 102 if __grains__["os"] in ( 103 "RedHat", 104 "CentOS", 105 "ScientificLinux", 106 "OEL", 107 "CloudLinux", 108 ): 109 if osrelease_major >= 7: 110 return ( 111 False, 112 "RedHat-based distros >= version 7 use systemd, will not " 113 "load rh_service.py as virtual 'service'", 114 ) 115 return __virtualname__ 116 return (False, "Cannot load rh_service module: OS not in {}".format(enable)) 117 118 119def _runlevel(): 120 """ 121 Return the current runlevel 122 """ 123 out = __salt__["cmd.run"]("/sbin/runlevel") 124 # unknown will be returned while inside a kickstart environment, since 125 # this is usually a server deployment it should be safe to assume runlevel 126 # 3. If not all service related states will throw an out of range 127 # exception here which will cause other functions to fail. 128 if "unknown" in out: 129 return "3" 130 else: 131 return out.split()[1] 132 133 134def _chkconfig_add(name): 135 """ 136 Run 'chkconfig --add' for a service whose script is installed in 137 /etc/init.d. The service is initially configured to be disabled at all 138 run-levels. 139 """ 140 cmd = "/sbin/chkconfig --add {}".format(name) 141 if __salt__["cmd.retcode"](cmd, python_shell=False) == 0: 142 log.info('Added initscript "%s" to chkconfig', name) 143 return True 144 else: 145 log.error('Unable to add initscript "%s" to chkconfig', name) 146 return False 147 148 149def _service_is_upstart(name): 150 """ 151 Return True if the service is an upstart service, otherwise return False. 152 """ 153 return HAS_UPSTART and os.path.exists("/etc/init/{}.conf".format(name)) 154 155 156def _service_is_sysv(name): 157 """ 158 Return True if the service is a System V service (includes those managed by 159 chkconfig); otherwise return False. 160 """ 161 try: 162 # Look for user-execute bit in file mode. 163 return bool(os.stat(os.path.join("/etc/init.d", name)).st_mode & stat.S_IXUSR) 164 except OSError: 165 return False 166 167 168def _service_is_chkconfig(name): 169 """ 170 Return True if the service is managed by chkconfig. 171 """ 172 cmdline = "/sbin/chkconfig --list {}".format(name) 173 return ( 174 __salt__["cmd.retcode"](cmdline, python_shell=False, ignore_retcode=True) == 0 175 ) 176 177 178def _sysv_is_enabled(name, runlevel=None): 179 """ 180 Return True if the sysv (or chkconfig) service is enabled for the specified 181 runlevel; otherwise return False. If `runlevel` is None, then use the 182 current runlevel. 183 """ 184 # Try chkconfig first. 185 result = _chkconfig_is_enabled(name, runlevel) 186 if result: 187 return True 188 189 if runlevel is None: 190 runlevel = _runlevel() 191 return len(glob.glob("/etc/rc.d/rc{}.d/S??{}".format(runlevel, name))) > 0 192 193 194def _chkconfig_is_enabled(name, runlevel=None): 195 """ 196 Return ``True`` if the service is enabled according to chkconfig; otherwise 197 return ``False``. If ``runlevel`` is ``None``, then use the current 198 runlevel. 199 """ 200 cmdline = "/sbin/chkconfig --list {}".format(name) 201 result = __salt__["cmd.run_all"](cmdline, python_shell=False) 202 203 if runlevel is None: 204 runlevel = _runlevel() 205 if result["retcode"] == 0: 206 for row in result["stdout"].splitlines(): 207 if "{}:on".format(runlevel) in row: 208 if row.split()[0] == name: 209 return True 210 elif row.split() == [name, "on"]: 211 return True 212 return False 213 214 215def _sysv_enable(name): 216 """ 217 Enable the named sysv service to start at boot. The service will be enabled 218 using chkconfig with default run-levels if the service is chkconfig 219 compatible. If chkconfig is not available, then this will fail. 220 """ 221 if not _service_is_chkconfig(name) and not _chkconfig_add(name): 222 return False 223 cmd = "/sbin/chkconfig {} on".format(name) 224 return not __salt__["cmd.retcode"](cmd, python_shell=False) 225 226 227def _sysv_disable(name): 228 """ 229 Disable the named sysv service from starting at boot. The service will be 230 disabled using chkconfig with default run-levels if the service is chkconfig 231 compatible; otherwise, the service will be disabled for the current 232 run-level only. 233 """ 234 if not _service_is_chkconfig(name) and not _chkconfig_add(name): 235 return False 236 cmd = "/sbin/chkconfig {} off".format(name) 237 return not __salt__["cmd.retcode"](cmd, python_shell=False) 238 239 240def _sysv_delete(name): 241 """ 242 Delete the named sysv service from the system. The service will be 243 deleted using chkconfig. 244 """ 245 if not _service_is_chkconfig(name): 246 return False 247 cmd = "/sbin/chkconfig --del {}".format(name) 248 return not __salt__["cmd.retcode"](cmd) 249 250 251def _upstart_delete(name): 252 """ 253 Delete an upstart service. This will only rename the .conf file 254 """ 255 if HAS_UPSTART: 256 if os.path.exists("/etc/init/{}.conf".format(name)): 257 os.rename( 258 "/etc/init/{}.conf".format(name), 259 "/etc/init/{}.conf.removed".format(name), 260 ) 261 return True 262 263 264def _upstart_services(): 265 """ 266 Return list of upstart services. 267 """ 268 if HAS_UPSTART: 269 return [os.path.basename(name)[:-5] for name in glob.glob("/etc/init/*.conf")] 270 else: 271 return [] 272 273 274def _sysv_services(): 275 """ 276 Return list of sysv services. 277 """ 278 _services = [] 279 output = __salt__["cmd.run"](["chkconfig", "--list"], python_shell=False) 280 for line in output.splitlines(): 281 comps = line.split() 282 try: 283 if comps[1].startswith("0:"): 284 _services.append(comps[0]) 285 except IndexError: 286 continue 287 # Return only the services that have an initscript present 288 return [x for x in _services if _service_is_sysv(x)] 289 290 291def get_enabled(limit=""): 292 """ 293 Return the enabled services. Use the ``limit`` param to restrict results 294 to services of that type. 295 296 CLI Examples: 297 298 .. code-block:: bash 299 300 salt '*' service.get_enabled 301 salt '*' service.get_enabled limit=upstart 302 salt '*' service.get_enabled limit=sysvinit 303 """ 304 limit = limit.lower() 305 if limit == "upstart": 306 return sorted(name for name in _upstart_services() if _upstart_is_enabled(name)) 307 elif limit == "sysvinit": 308 runlevel = _runlevel() 309 return sorted( 310 name for name in _sysv_services() if _sysv_is_enabled(name, runlevel) 311 ) 312 else: 313 runlevel = _runlevel() 314 return sorted( 315 [name for name in _upstart_services() if _upstart_is_enabled(name)] 316 + [name for name in _sysv_services() if _sysv_is_enabled(name, runlevel)] 317 ) 318 319 320def get_disabled(limit=""): 321 """ 322 Return the disabled services. Use the ``limit`` param to restrict results 323 to services of that type. 324 325 CLI Example: 326 327 .. code-block:: bash 328 329 salt '*' service.get_disabled 330 salt '*' service.get_disabled limit=upstart 331 salt '*' service.get_disabled limit=sysvinit 332 """ 333 limit = limit.lower() 334 if limit == "upstart": 335 return sorted( 336 name for name in _upstart_services() if not _upstart_is_enabled(name) 337 ) 338 elif limit == "sysvinit": 339 runlevel = _runlevel() 340 return sorted( 341 name for name in _sysv_services() if not _sysv_is_enabled(name, runlevel) 342 ) 343 else: 344 runlevel = _runlevel() 345 return sorted( 346 [name for name in _upstart_services() if not _upstart_is_enabled(name)] 347 + [ 348 name 349 for name in _sysv_services() 350 if not _sysv_is_enabled(name, runlevel) 351 ] 352 ) 353 354 355def get_all(limit=""): 356 """ 357 Return all installed services. Use the ``limit`` param to restrict results 358 to services of that type. 359 360 CLI Example: 361 362 .. code-block:: bash 363 364 salt '*' service.get_all 365 salt '*' service.get_all limit=upstart 366 salt '*' service.get_all limit=sysvinit 367 """ 368 limit = limit.lower() 369 if limit == "upstart": 370 return sorted(_upstart_services()) 371 elif limit == "sysvinit": 372 return sorted(_sysv_services()) 373 else: 374 return sorted(_sysv_services() + _upstart_services()) 375 376 377def available(name, limit=""): 378 """ 379 Return True if the named service is available. Use the ``limit`` param to 380 restrict results to services of that type. 381 382 CLI Examples: 383 384 .. code-block:: bash 385 386 salt '*' service.available sshd 387 salt '*' service.available sshd limit=upstart 388 salt '*' service.available sshd limit=sysvinit 389 """ 390 if limit == "upstart": 391 return _service_is_upstart(name) 392 elif limit == "sysvinit": 393 return _service_is_sysv(name) 394 else: 395 return ( 396 _service_is_upstart(name) 397 or _service_is_sysv(name) 398 or _service_is_chkconfig(name) 399 ) 400 401 402def missing(name, limit=""): 403 """ 404 The inverse of service.available. 405 Return True if the named service is not available. Use the ``limit`` param to 406 restrict results to services of that type. 407 408 CLI Examples: 409 410 .. code-block:: bash 411 412 salt '*' service.missing sshd 413 salt '*' service.missing sshd limit=upstart 414 salt '*' service.missing sshd limit=sysvinit 415 """ 416 if limit == "upstart": 417 return not _service_is_upstart(name) 418 elif limit == "sysvinit": 419 return not _service_is_sysv(name) 420 else: 421 if _service_is_upstart(name) or _service_is_sysv(name): 422 return False 423 else: 424 return True 425 426 427def start(name): 428 """ 429 Start the specified service 430 431 CLI Example: 432 433 .. code-block:: bash 434 435 salt '*' service.start <service name> 436 """ 437 if _service_is_upstart(name): 438 cmd = "start {}".format(name) 439 else: 440 cmd = "/sbin/service {} start".format(name) 441 return not __salt__["cmd.retcode"](cmd, python_shell=False) 442 443 444def stop(name): 445 """ 446 Stop the specified service 447 448 CLI Example: 449 450 .. code-block:: bash 451 452 salt '*' service.stop <service name> 453 """ 454 if _service_is_upstart(name): 455 cmd = "stop {}".format(name) 456 else: 457 cmd = "/sbin/service {} stop".format(name) 458 return not __salt__["cmd.retcode"](cmd, python_shell=False) 459 460 461def restart(name): 462 """ 463 Restart the named service 464 465 CLI Example: 466 467 .. code-block:: bash 468 469 salt '*' service.restart <service name> 470 """ 471 if _service_is_upstart(name): 472 cmd = "restart {}".format(name) 473 else: 474 cmd = "/sbin/service {} restart".format(name) 475 return not __salt__["cmd.retcode"](cmd, python_shell=False) 476 477 478def reload_(name): 479 """ 480 Reload the named service 481 482 CLI Example: 483 484 .. code-block:: bash 485 486 salt '*' service.reload <service name> 487 """ 488 if _service_is_upstart(name): 489 cmd = "reload {}".format(name) 490 else: 491 cmd = "/sbin/service {} reload".format(name) 492 return not __salt__["cmd.retcode"](cmd, python_shell=False) 493 494 495def status(name, sig=None): 496 """ 497 Return the status for a service. 498 If the name contains globbing, a dict mapping service name to True/False 499 values is returned. 500 501 .. versionchanged:: 2018.3.0 502 The service name can now be a glob (e.g. ``salt*``) 503 504 Args: 505 name (str): The name of the service to check 506 sig (str): Signature to use to find the service via ps 507 508 Returns: 509 bool: True if running, False otherwise 510 dict: Maps service name to True if running, False otherwise 511 512 CLI Example: 513 514 .. code-block:: bash 515 516 salt '*' service.status <service name> [service signature] 517 """ 518 if sig: 519 return bool(__salt__["status.pid"](sig)) 520 521 contains_globbing = bool(re.search(r"\*|\?|\[.+\]", name)) 522 if contains_globbing: 523 services = fnmatch.filter(get_all(), name) 524 else: 525 services = [name] 526 results = {} 527 for service in services: 528 if _service_is_upstart(service): 529 cmd = "status {}".format(service) 530 results[service] = "start/running" in __salt__["cmd.run"]( 531 cmd, python_shell=False 532 ) 533 else: 534 cmd = "/sbin/service {} status".format(service) 535 results[service] = ( 536 __salt__["cmd.retcode"](cmd, python_shell=False, ignore_retcode=True) 537 == 0 538 ) 539 if contains_globbing: 540 return results 541 return results[name] 542 543 544def delete(name, **kwargs): 545 """ 546 Delete the named service 547 548 .. versionadded:: 2016.3 549 550 CLI Example: 551 552 .. code-block:: bash 553 554 salt '*' service.delete <service name> 555 """ 556 if _service_is_upstart(name): 557 return _upstart_delete(name) 558 else: 559 return _sysv_delete(name) 560 561 562def enable(name, **kwargs): 563 """ 564 Enable the named service to start at boot 565 566 CLI Example: 567 568 .. code-block:: bash 569 570 salt '*' service.enable <service name> 571 """ 572 if _service_is_upstart(name): 573 return _upstart_enable(name) 574 else: 575 return _sysv_enable(name) 576 577 578def disable(name, **kwargs): 579 """ 580 Disable the named service to start at boot 581 582 CLI Example: 583 584 .. code-block:: bash 585 586 salt '*' service.disable <service name> 587 """ 588 if _service_is_upstart(name): 589 return _upstart_disable(name) 590 else: 591 return _sysv_disable(name) 592 593 594def enabled(name, **kwargs): 595 """ 596 Check to see if the named service is enabled to start on boot 597 598 CLI Example: 599 600 .. code-block:: bash 601 602 salt '*' service.enabled <service name> 603 """ 604 if _service_is_upstart(name): 605 return _upstart_is_enabled(name) 606 else: 607 return _sysv_is_enabled(name) 608 609 610def disabled(name): 611 """ 612 Check to see if the named service is disabled to start on boot 613 614 CLI Example: 615 616 .. code-block:: bash 617 618 salt '*' service.disabled <service name> 619 """ 620 if _service_is_upstart(name): 621 return not _upstart_is_enabled(name) 622 else: 623 return not _sysv_is_enabled(name) 624