Source code for mac_maker.ansible_controller.process
"""Process management for Ansible commands."""
import importlib
import logging
import os
import shlex
import traceback
import click
from .. import config
from ..utilities.shell import cmd_loop
from ..utilities.state import TypeState
from . import environment
[docs]class AnsibleProcess:
"""Process management for Ansible commands.
:param ansible_module: Dot path of the ansible module to load.
:param ansible_class: The Ansible class to import from this module.
:param state: The loaded runtime state object.
"""
error_exit_code = 127
def __init__(
self, ansible_module: str, ansible_class: str, state: TypeState
) -> None:
self.log = logging.getLogger(config.LOGGER_NAME)
self.state = state
self.ansible_class = ansible_class
self.ansible_module = ansible_module
[docs] def spawn(self, command: str) -> None:
"""Spawns an Ansible CLI Command in it's own process.
:param command: The Ansible CLI Command to spawn.
"""
self.log.debug("AnsibleProcess: Preparing to Fork for Ansible Process.")
pid = os.fork()
if pid == 0:
self._forked_process(command, pid)
else:
self._main_process(command, pid)
def _forked_process(self, command: str, pid: int) -> None:
try:
self.log.debug(
"AnsibleProcess - PID: %s: Forked process is now executing: %s.",
pid,
command,
)
self._environment()
self._execution_location()
ansible_cli_module = importlib.import_module(self.ansible_module)
ansible_cli_class = getattr(ansible_cli_module, self.ansible_class)
instance = ansible_cli_class(shlex.split(command))
self.log.debug(
(
"AnsibleProcess - PID: %s: Forked process Ansible CLI Class "
"instance has been created: %s."
),
pid,
str(instance),
)
try:
self.log.debug(
(
"AnsibleProcess - PID: %s: Forked process Ansible CLI Class "
"instance is calling run."
),
pid,
)
instance.run()
self.log.debug(
"AnsibleProcess - PID: %s: Forked process has finished.",
pid,
)
cmd_loop.exit_shell(0, pid)
except Exception: # pylint: disable=broad-exception-caught
traceback.print_exc()
cmd_loop.exit_shell(self.error_exit_code, pid)
except KeyboardInterrupt:
cmd_loop.exit_shell(self.error_exit_code, pid)
def _environment(self) -> None:
env = environment.Environment(self.state)
env.setup()
def _execution_location(self) -> None:
os.chdir(self.state['profile_data_path'])
def _main_process(self, command: str, pid: int) -> None:
try:
_, exit_status = os.waitpid(pid, 0)
status_code = os.WEXITSTATUS(exit_status)
self.log.debug(
"AnsibleProcess - PID: %s: Waited, and received exit code: %s.",
pid,
status_code,
)
if status_code:
click.echo("ANSIBLE ERROR: Non zero exit code.")
click.echo(f"COMMAND: {command}")
self.log.error(
(
"AnsibleProcess - PID: %s: Forked process has reported an "
"error state."
),
pid,
)
cmd_loop.exit(status_code, pid)
else:
self.log.debug(
(
"AnsibleProcess - PID: %s: Forked process has reported no "
"error state."
),
pid,
)
except KeyboardInterrupt:
self.log.error(
"AnsibleProcess - PID: %s: Keyboard Interrupt Intercepted.",
pid,
)
cmd_loop.exit(self.error_exit_code, pid)