PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/container_resource.py |
""" Common resources for LXC and systemd-nspawn containers .. versionadded:: 2015.8.0 These functions are not designed to be called directly, but instead from the :mod:`lxc <salt.modules.lxc>`, :mod:`nspawn <salt.modules.nspawn>`, and :mod:`docker <salt.modules.docker>` execution modules. They provide for common logic to be re-used for common actions. """ import copy import functools import logging import os import shlex import time import traceback import salt.utils.args import salt.utils.path import salt.utils.vt from salt.exceptions import CommandExecutionError, SaltInvocationError log = logging.getLogger(__name__) PATH = "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/opt/bin:/usr/local/bin:/usr/local/sbin" def _validate(wrapped): """ Decorator for common function argument validation """ @functools.wraps(wrapped) def wrapper(*args, **kwargs): container_type = kwargs.get("container_type") exec_driver = kwargs.get("exec_driver") valid_driver = { "docker": ("lxc-attach", "nsenter", "docker-exec"), "lxc": ("lxc-attach",), "nspawn": ("nsenter",), } if container_type not in valid_driver: raise SaltInvocationError( "Invalid container type '{}'. Valid types are: {}".format( container_type, ", ".join(sorted(valid_driver)) ) ) if exec_driver not in valid_driver[container_type]: raise SaltInvocationError( "Invalid command execution driver. Valid drivers are: {}".format( ", ".join(valid_driver[container_type]) ) ) if exec_driver == "lxc-attach" and not salt.utils.path.which("lxc-attach"): raise SaltInvocationError( "The 'lxc-attach' execution driver has been chosen, but " "lxc-attach is not available. LXC may not be installed." ) return wrapped(*args, **salt.utils.args.clean_kwargs(**kwargs)) return wrapper def _nsenter(pid): """ Return the nsenter command to attach to the named container """ return f"nsenter --target {pid} --mount --uts --ipc --net --pid" def _get_sha256(name, path, run_func): """ Get the sha256 checksum of a file from a container """ ret = run_func(name, f"sha256sum {shlex.quote(path)}", ignore_retcode=True) try: return ret["stdout"].split()[0] except IndexError: # Destination file does not exist or could not be accessed return None def cache_file(source): """ Wrapper for cp.cache_file which raises an error if the file was unable to be cached. CLI Example: .. code-block:: bash salt myminion container_resource.cache_file salt://foo/bar/baz.txt """ try: # Don't just use cp.cache_file for this. Docker has its own code to # pull down images from the web. if source.startswith("salt://"): cached_source = __salt__["cp.cache_file"](source) if not cached_source: raise CommandExecutionError(f"Unable to cache {source}") return cached_source except AttributeError: raise SaltInvocationError(f"Invalid source file {source}") return source @_validate def run( name, cmd, container_type=None, exec_driver=None, output=None, no_start=False, stdin=None, python_shell=True, output_loglevel="debug", ignore_retcode=False, path=None, use_vt=False, keep_env=None, ): """ Common logic for running shell commands in containers path path to the container parent (for LXC only) default: /var/lib/lxc (system default) CLI Example: .. code-block:: bash salt myminion container_resource.run mycontainer 'ps aux' container_type=docker exec_driver=nsenter output=stdout """ valid_output = ("stdout", "stderr", "retcode", "all") if output is None: cmd_func = "cmd.run" elif output not in valid_output: raise SaltInvocationError( "'output' param must be one of the following: {}".format( ", ".join(valid_output) ) ) else: cmd_func = "cmd.run_all" if keep_env is None or isinstance(keep_env, bool): to_keep = [] elif not isinstance(keep_env, (list, tuple)): try: to_keep = keep_env.split(",") except AttributeError: log.warning("Invalid keep_env value, ignoring") to_keep = [] else: to_keep = keep_env if exec_driver == "lxc-attach": full_cmd = "lxc-attach " if path: full_cmd += f"-P {shlex.quote(path)} " if keep_env is not True: full_cmd += "--clear-env " if "PATH" not in to_keep: full_cmd += f"--set-var {PATH} " # --clear-env results in a very restrictive PATH # (/bin:/usr/bin), use a good fallback. full_cmd += " ".join( [ f"--set-var {x}={shlex.quote(os.environ[x])}" for x in to_keep if x in os.environ ] ) full_cmd += f" -n {shlex.quote(name)} -- {cmd}" elif exec_driver == "nsenter": pid = __salt__[f"{container_type}.pid"](name) full_cmd = f"nsenter --target {pid} --mount --uts --ipc --net --pid -- " if keep_env is not True: full_cmd += "env -i " if "PATH" not in to_keep: full_cmd += f"{PATH} " full_cmd += " ".join( [f"{x}={shlex.quote(os.environ[x])}" for x in to_keep if x in os.environ] ) full_cmd += f" {cmd}" elif exec_driver == "docker-exec": # We're using docker exec on the CLI as opposed to via docker-py, since # the Docker API doesn't return stdout and stderr separately. full_cmd = "docker exec " if stdin: full_cmd += "-i " full_cmd += f"{name} " if keep_env is not True: full_cmd += "env -i " if "PATH" not in to_keep: full_cmd += f"{PATH} " full_cmd += " ".join( [f"{x}={shlex.quote(os.environ[x])}" for x in to_keep if x in os.environ] ) full_cmd += f" {cmd}" if not use_vt: ret = __salt__[cmd_func]( full_cmd, stdin=stdin, python_shell=python_shell, output_loglevel=output_loglevel, ignore_retcode=ignore_retcode, ) else: stdout, stderr = "", "" proc = salt.utils.vt.Terminal( full_cmd, shell=python_shell, log_stdin_level="quiet" if output_loglevel == "quiet" else "info", log_stdout_level=output_loglevel, log_stderr_level=output_loglevel, log_stdout=True, log_stderr=True, stream_stdout=False, stream_stderr=False, ) # Consume output try: while proc.has_unread_data: try: cstdout, cstderr = proc.recv() if cstdout: stdout += cstdout if cstderr: if output is None: stdout += cstderr else: stderr += cstderr time.sleep(0.5) except KeyboardInterrupt: break ret = ( stdout if output is None else { "retcode": proc.exitstatus, "pid": 2, "stdout": stdout, "stderr": stderr, } ) except salt.utils.vt.TerminalException: trace = traceback.format_exc() log.error(trace) ret = ( stdout if output is None else {"retcode": 127, "pid": 2, "stdout": stdout, "stderr": stderr} ) finally: proc.terminate() return ret @_validate def copy_to( name, source, dest, container_type=None, path=None, exec_driver=None, overwrite=False, makedirs=False, ): """ Common logic for copying files to containers path path to the container parent (for LXC only) default: /var/lib/lxc (system default) CLI Example: .. code-block:: bash salt myminion container_resource.copy_to mycontainer /local/file/path /container/file/path container_type=docker exec_driver=nsenter """ # Get the appropriate functions state = __salt__[f"{container_type}.state"] def run_all(*args, **akwargs): akwargs = copy.deepcopy(akwargs) if container_type in ["lxc"] and "path" not in akwargs: akwargs["path"] = path return __salt__[f"{container_type}.run_all"](*args, **akwargs) state_kwargs = {} cmd_kwargs = {"ignore_retcode": True} if container_type in ["lxc"]: cmd_kwargs["path"] = path state_kwargs["path"] = path def _state(name): if state_kwargs: return state(name, **state_kwargs) else: return state(name) c_state = _state(name) if c_state != "running": raise CommandExecutionError(f"Container '{name}' is not running") local_file = cache_file(source) source_dir, source_name = os.path.split(local_file) # Source file sanity checks if not os.path.isabs(local_file): raise SaltInvocationError("Source path must be absolute") elif not os.path.exists(local_file): raise SaltInvocationError(f"Source file {local_file} does not exist") elif not os.path.isfile(local_file): raise SaltInvocationError("Source must be a regular file") # Destination file sanity checks if not os.path.isabs(dest): raise SaltInvocationError("Destination path must be absolute") if run_all(name, f"test -d {shlex.quote(dest)}", **cmd_kwargs)["retcode"] == 0: # Destination is a directory, full path to dest file will include the # basename of the source file. dest = os.path.join(dest, source_name) else: # Destination was not a directory. We will check to see if the parent # dir is a directory, and then (if makedirs=True) attempt to create the # parent directory. dest_dir, dest_name = os.path.split(dest) if ( run_all(name, f"test -d {shlex.quote(dest_dir)}", **cmd_kwargs)["retcode"] != 0 ): if makedirs: result = run_all( name, f"mkdir -p {shlex.quote(dest_dir)}", **cmd_kwargs ) if result["retcode"] != 0: error = ( "Unable to create destination directory {} in " "container '{}'".format(dest_dir, name) ) if result["stderr"]: error += ": {}".format(result["stderr"]) raise CommandExecutionError(error) else: raise SaltInvocationError( "Directory {} does not exist on {} container '{}'".format( dest_dir, container_type, name ) ) if ( not overwrite and run_all(name, f"test -e {shlex.quote(dest)}", **cmd_kwargs)["retcode"] == 0 ): raise CommandExecutionError( "Destination path {} already exists. Use overwrite=True to " "overwrite it".format(dest) ) # Before we try to replace the file, compare checksums. source_sha256 = __salt__["file.get_sum"](local_file, "sha256") if source_sha256 == _get_sha256(name, dest, run_all): log.debug("%s and %s:%s are the same file, skipping copy", source, name, dest) return True log.debug( "Copying %s to %s container '%s' as %s", source, container_type, name, dest ) # Using cat here instead of opening the file, reading it into memory, # and passing it as stdin to run(). This will keep down memory # usage for the minion and make the operation run quicker. if exec_driver == "lxc-attach": lxcattach = "lxc-attach" if path: lxcattach += f" -P {shlex.quote(path)}" copy_cmd = ( 'cat "{0}" | {4} --clear-env --set-var {1} -n {2} -- tee "{3}"'.format( local_file, PATH, name, dest, lxcattach ) ) elif exec_driver == "nsenter": pid = __salt__[f"{container_type}.pid"](name) copy_cmd = 'cat "{}" | {} env -i {} tee "{}"'.format( local_file, _nsenter(pid), PATH, dest ) elif exec_driver == "docker-exec": copy_cmd = 'cat "{}" | docker exec -i {} env -i {} tee "{}"'.format( local_file, name, PATH, dest ) __salt__["cmd.run"](copy_cmd, python_shell=True, output_loglevel="quiet") return source_sha256 == _get_sha256(name, dest, run_all)