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/cron.py |
""" Work with cron .. note:: Salt does not escape cron metacharacters automatically. You should backslash-escape percent characters and any other metacharacters that might be interpreted incorrectly by the shell. """ import logging import os import random import salt.utils.data import salt.utils.files import salt.utils.functools import salt.utils.path import salt.utils.stringutils TAG = "# Lines below here are managed by Salt, do not edit\n" SALT_CRON_IDENTIFIER = "SALT_CRON_IDENTIFIER" SALT_CRON_NO_IDENTIFIER = "NO ID SET" log = logging.getLogger(__name__) def __virtual__(): if salt.utils.path.which("crontab"): return True else: return (False, "Cannot load cron module: crontab command not found") def _ensure_string(val): # Account for cases where the identifier is not a string # which would cause to_unicode to fail. if not isinstance(val, str): val = str(val) try: return salt.utils.stringutils.to_unicode(val) except TypeError: return "" def _cron_id(cron): """SAFETYBELT, Only set if we really have an identifier""" cid = None if cron["identifier"]: cid = cron["identifier"] else: cid = SALT_CRON_NO_IDENTIFIER if cid: return _ensure_string(cid) def _cron_matched(cron, cmd, identifier=None): """Check if: - we find a cron with same cmd, old state behavior - but also be smart enough to remove states changed crons where we do not removed priorly by a cron.absent by matching on the provided identifier. We assure retrocompatibility by only checking on identifier if and only if an identifier was set on the serialized crontab """ ret, id_matched = False, None cid = _cron_id(cron) if cid: if not identifier: identifier = SALT_CRON_NO_IDENTIFIER eidentifier = _ensure_string(identifier) # old style second round # after saving crontab, we must check that if # we have not the same command, but the default id # to not set that as a match if ( cron.get("cmd", None) != cmd and cid == SALT_CRON_NO_IDENTIFIER and eidentifier == SALT_CRON_NO_IDENTIFIER ): id_matched = False else: # on saving, be sure not to overwrite a cron # with specific identifier but also track # crons where command is the same # but with the default if that we gonna overwrite if ( cron.get("cmd", None) == cmd and cid == SALT_CRON_NO_IDENTIFIER and identifier ): cid = eidentifier id_matched = eidentifier == cid if ((id_matched is None) and cmd == cron.get("cmd", None)) or id_matched: ret = True return ret def _needs_change(old, new): if old != new: if new == "random": # Allow switch from '*' or not present to 'random' if old == "*": return True elif new is not None: return True return False def _render_tab(lst): """ Takes a tab list structure and renders it to a list for applying it to a file """ ret = [] for pre in lst["pre"]: ret.append(f"{pre}\n") if ret: if ret[-1] != TAG: ret.append(TAG) else: ret.append(TAG) for env in lst["env"]: if (env["value"] is None) or (env["value"] == ""): ret.append('{}=""\n'.format(env["name"])) else: ret.append("{}={}\n".format(env["name"], env["value"])) for cron in lst["crons"]: if cron["comment"] is not None or cron["identifier"] is not None: comment = "#" if cron["comment"]: comment += " {}".format(cron["comment"].replace("\n", "\n# ")) if cron["identifier"]: comment += " {}:{}".format(SALT_CRON_IDENTIFIER, cron["identifier"]) comment += "\n" ret.append(comment) ret.append( "{}{} {} {} {} {} {}\n".format( cron["commented"] is True and "#DISABLED#" or "", cron["minute"], cron["hour"], cron["daymonth"], cron["month"], cron["dayweek"], cron["cmd"], ) ) for cron in lst["special"]: if cron["comment"] is not None or cron["identifier"] is not None: comment = "#" if cron["comment"]: comment += " {}".format(cron["comment"].rstrip().replace("\n", "\n# ")) if cron["identifier"]: comment += " {}:{}".format(SALT_CRON_IDENTIFIER, cron["identifier"]) comment += "\n" ret.append(comment) ret.append( "{}{} {}\n".format( cron["commented"] is True and "#DISABLED#" or "", cron["spec"], cron["cmd"], ) ) return ret def _get_cron_cmdstr(path, user=None): """ Returns a format string, to be used to build a crontab command. """ if user: cmd = f"crontab -u {user}" else: cmd = "crontab" return f"{cmd} {path}" def _check_instance_uid_match(user): """ Returns true if running instance's UID matches the specified user UID """ return os.geteuid() == __salt__["file.user_to_uid"](user) def write_cron_file(user, path): """ Writes the contents of a file to a user's crontab CLI Example: .. code-block:: bash salt '*' cron.write_cron_file root /tmp/new_cron .. versionchanged:: 2015.8.9 .. note:: Some OS' do not support specifying user via the `crontab` command i.e. (Solaris, AIX) """ # Some OS' do not support specifying user via the `crontab` command if __grains__.get("os_family") in ("Solaris", "AIX"): return ( __salt__["cmd.retcode"]( _get_cron_cmdstr(path), runas=user, python_shell=False ) == 0 ) # If Salt is running from same user as requested in cron module we don't need any user switch elif _check_instance_uid_match(user): return __salt__["cmd.retcode"](_get_cron_cmdstr(path), python_shell=False) == 0 # If Salt is running from root user it could modify any user's crontab elif _check_instance_uid_match("root"): return ( __salt__["cmd.retcode"](_get_cron_cmdstr(path, user), python_shell=False) == 0 ) # Edge cases here, let's try do a runas else: return ( __salt__["cmd.retcode"]( _get_cron_cmdstr(path), runas=user, python_shell=False ) == 0 ) def write_cron_file_verbose(user, path): """ Writes the contents of a file to a user's crontab and return error message on error CLI Example: .. code-block:: bash salt '*' cron.write_cron_file_verbose root /tmp/new_cron .. versionchanged:: 2015.8.9 .. note:: Some OS' do not support specifying user via the `crontab` command i.e. (Solaris, AIX) """ # Some OS' do not support specifying user via the `crontab` command if __grains__.get("os_family") in ("Solaris", "AIX"): return __salt__["cmd.run_all"]( _get_cron_cmdstr(path), runas=user, python_shell=False ) # If Salt is running from same user as requested in cron module we don't need any user switch elif _check_instance_uid_match(user): return __salt__["cmd.run_all"](_get_cron_cmdstr(path), python_shell=False) # If Salt is running from root user it could modify any user's crontab elif _check_instance_uid_match("root"): return __salt__["cmd.run_all"](_get_cron_cmdstr(path, user), python_shell=False) # Edge cases here, let's try do a runas else: return __salt__["cmd.run_all"]( _get_cron_cmdstr(path), runas=user, python_shell=False ) def _write_cron_lines(user, lines): """ Takes a list of lines to be committed to a user's crontab and writes it """ lines = [salt.utils.stringutils.to_str(_l) for _l in lines] path = salt.utils.files.mkstemp() # Some OS' do not support specifying user via the `crontab` command if __grains__.get("os_family") in ("Solaris", "AIX"): with salt.utils.files.fpopen( path, "w+", uid=__salt__["file.user_to_uid"](user), mode=0o600 ) as fp_: fp_.writelines(lines) ret = __salt__["cmd.run_all"]( _get_cron_cmdstr(path), runas=user, python_shell=False ) # If Salt is running from same user as requested in cron module we don't need any user switch elif _check_instance_uid_match(user): with salt.utils.files.fpopen(path, "w+", mode=0o600) as fp_: fp_.writelines(lines) ret = __salt__["cmd.run_all"](_get_cron_cmdstr(path), python_shell=False) # If Salt is running from root user it could modify any user's crontab elif _check_instance_uid_match("root"): with salt.utils.files.fpopen(path, "w+", mode=0o600) as fp_: fp_.writelines(lines) ret = __salt__["cmd.run_all"](_get_cron_cmdstr(path, user), python_shell=False) # Edge cases here, let's try do a runas else: with salt.utils.files.fpopen( path, "w+", uid=__salt__["file.user_to_uid"](user), mode=0o600 ) as fp_: fp_.writelines(lines) ret = __salt__["cmd.run_all"]( _get_cron_cmdstr(path), runas=user, python_shell=False ) os.remove(path) return ret def _date_time_match(cron, **kwargs): """ Returns true if the minute, hour, etc. params match their counterparts from the dict returned from list_tab(). """ return all( [ kwargs.get(x) is None or cron[x] == str(kwargs[x]) or (str(kwargs[x]).lower() == "random" and cron[x] != "*") for x in ("minute", "hour", "daymonth", "month", "dayweek") ] ) def raw_cron(user): """ Return the contents of the user's crontab CLI Example: .. code-block:: bash salt '*' cron.raw_cron root """ # Some OS' do not support specifying user via the `crontab` command if __grains__.get("os_family") in ("Solaris", "AIX"): cmd = "crontab -l" # Preserve line endings lines = salt.utils.data.decode( __salt__["cmd.run_stdout"]( cmd, runas=user, ignore_retcode=True, rstrip=False, python_shell=False ) ).splitlines(True) # If Salt is running from same user as requested in cron module we don't need any user switch elif _check_instance_uid_match(user): cmd = "crontab -l" # Preserve line endings lines = salt.utils.data.decode( __salt__["cmd.run_stdout"]( cmd, ignore_retcode=True, rstrip=False, python_shell=False ) ).splitlines(True) # If Salt is running from root user it could modify any user's crontab elif _check_instance_uid_match("root"): cmd = f"crontab -u {user} -l" # Preserve line endings lines = salt.utils.data.decode( __salt__["cmd.run_stdout"]( cmd, ignore_retcode=True, rstrip=False, python_shell=False ) ).splitlines(True) # Edge cases here, let's try do a runas else: cmd = "crontab -l" # Preserve line endings lines = salt.utils.data.decode( __salt__["cmd.run_stdout"]( cmd, runas=user, ignore_retcode=True, rstrip=False, python_shell=False ) ).splitlines(True) if lines and lines[0].startswith( "# DO NOT EDIT THIS FILE - edit the master and reinstall." ): del lines[0:3] return "".join(lines) def list_tab(user): """ Return the contents of the specified user's crontab CLI Example: .. code-block:: bash salt '*' cron.list_tab root """ data = raw_cron(user) ret = {"pre": [], "crons": [], "special": [], "env": []} flag = False comment = None identifier = None for line in data.splitlines(): if line == "# Lines below here are managed by Salt, do not edit": flag = True continue if flag: commented_cron_job = False if line.startswith("#DISABLED#"): # It's a commented cron job line = line[10:] commented_cron_job = True if line.startswith("@"): # Its a "special" line dat = {} comps = line.split() if len(comps) < 2: # Invalid line continue dat["spec"] = comps[0] dat["cmd"] = " ".join(comps[1:]) dat["identifier"] = identifier dat["comment"] = comment dat["commented"] = False if commented_cron_job: dat["commented"] = True ret["special"].append(dat) identifier = None comment = None commented_cron_job = False elif line.startswith("#"): # It's a comment! Catch it! comment_line = line.lstrip("# ") # load the identifier if any if SALT_CRON_IDENTIFIER in comment_line: parts = comment_line.split(SALT_CRON_IDENTIFIER) comment_line = parts[0].rstrip() # skip leading : if len(parts[1]) > 1: identifier = parts[1][1:] if comment is None: comment = comment_line else: comment += "\n" + comment_line elif line.find("=") > 0 and ( " " not in line or line.index("=") < line.index(" ") ): # Appears to be a ENV setup line comps = line.split("=", 1) dat = {} dat["name"] = comps[0] dat["value"] = comps[1] ret["env"].append(dat) elif len(line.split(" ")) > 5: # Appears to be a standard cron line comps = line.split(" ") dat = { "minute": comps[0], "hour": comps[1], "daymonth": comps[2], "month": comps[3], "dayweek": comps[4], "identifier": identifier, "cmd": " ".join(comps[5:]), "comment": comment, "commented": False, } if commented_cron_job: dat["commented"] = True ret["crons"].append(dat) identifier = None comment = None commented_cron_job = False else: ret["pre"].append(line) return ret # For consistency's sake ls = salt.utils.functools.alias_function(list_tab, "ls") def get_entry(user, identifier=None, cmd=None): """ Return the specified entry from user's crontab. identifier will be used if specified, otherwise will lookup cmd Either identifier or cmd should be specified. user: User's crontab to query identifier: Search for line with identifier cmd: Search for cron line with cmd CLI Example: .. code-block:: bash salt '*' cron.get_entry root identifier=task1 """ if identifier and cmd: log.warning("Both identifier and cmd are specified. Only using identifier.") cmd = None cron_entries = list_tab(user).get("crons", []) + list_tab(user).get("special", []) for cron_entry in cron_entries: if identifier and cron_entry.get("identifier") == identifier: return cron_entry elif cmd and cron_entry.get("cmd") == cmd: return cron_entry return False def set_special(user, special, cmd, commented=False, comment=None, identifier=None): """ Set up a special command in the crontab. CLI Example: .. code-block:: bash salt '*' cron.set_special root @hourly 'echo foobar' """ lst = list_tab(user) for cron in lst["crons"] + lst["special"]: cid = _cron_id(cron) if _cron_matched(cron, cmd, identifier): test_setted_id = ( cron["identifier"] is None and SALT_CRON_NO_IDENTIFIER or cron["identifier"] ) tests = [ (cron["comment"], comment), (cron["commented"], commented), (identifier, test_setted_id), (cron.get("minute"), None), (cron.get("hour"), None), (cron.get("daymonth"), None), (cron.get("month"), None), (cron.get("dayweek"), None), (cron.get("spec"), special), ] if cid or identifier: tests.append((cron["cmd"], cmd)) if any([_needs_change(x, y) for x, y in tests]): if "spec" in cron: rm_special(user, cmd, identifier=cid) else: rm_job(user, cmd, identifier=cid) # Use old values when setting the new job if there was no # change needed for a given parameter if not _needs_change(cron.get("spec"), special): special = cron.get("spec") if not _needs_change(cron.get("commented"), commented): commented = cron.get("commented") if not _needs_change(cron.get("comment"), comment): comment = cron.get("comment") if not _needs_change(cron["cmd"], cmd): cmd = cron["cmd"] if cid == SALT_CRON_NO_IDENTIFIER: if identifier: cid = identifier if ( cid == SALT_CRON_NO_IDENTIFIER and cron["identifier"] is None ): cid = None cron["identifier"] = cid if not cid or (cid and not _needs_change(cid, identifier)): identifier = cid jret = set_special( user, special, cmd, commented=commented, comment=comment, identifier=identifier, ) if jret == "new": return "updated" else: return jret return "present" cron = { "spec": special, "cmd": cmd, "identifier": identifier, "comment": comment, "commented": commented, } lst["special"].append(cron) comdat = _write_cron_lines(user, _render_tab(lst)) if comdat["retcode"]: # Failed to commit, return the error return comdat["stderr"] return "new" def _get_cron_date_time(**kwargs): """ Returns a dict of date/time values to be used in a cron entry """ # Define ranges (except daymonth, as it depends on the month) range_max = { "minute": list(list(range(60))), "hour": list(list(range(24))), "month": list(list(range(1, 13))), "dayweek": list(list(range(7))), } ret = {} for param in ("minute", "hour", "month", "dayweek"): value = str(kwargs.get(param, "1")).lower() if value == "random": ret[param] = str(random.sample(range_max[param], 1)[0]) elif len(value.split(":")) == 2: cron_range = sorted(value.split(":")) start, end = int(cron_range[0]), int(cron_range[1]) ret[param] = str(random.randint(start, end)) else: ret[param] = value if ret["month"] in "1 3 5 7 8 10 12".split(): daymonth_max = 31 elif ret["month"] in "4 6 9 11".split(): daymonth_max = 30 else: # This catches both '2' and '*' daymonth_max = 28 daymonth = str(kwargs.get("daymonth", "1")).lower() if daymonth == "random": ret["daymonth"] = str( random.sample(list(list(range(1, (daymonth_max + 1)))), 1)[0] ) else: ret["daymonth"] = daymonth return ret def set_job( user, minute, hour, daymonth, month, dayweek, cmd, commented=False, comment=None, identifier=None, ): """ Sets a cron job up for a specified user. CLI Example: .. code-block:: bash salt '*' cron.set_job root '*' '*' '*' '*' 1 /usr/local/weekly """ # Scrub the types minute = str(minute).lower() hour = str(hour).lower() daymonth = str(daymonth).lower() month = str(month).lower() dayweek = str(dayweek).lower() lst = list_tab(user) for cron in lst["crons"] + lst["special"]: cid = _cron_id(cron) if _cron_matched(cron, cmd, identifier): test_setted_id = ( cron["identifier"] is None and SALT_CRON_NO_IDENTIFIER or cron["identifier"] ) tests = [ (cron["comment"], comment), (cron["commented"], commented), (identifier, test_setted_id), (cron.get("minute"), minute), (cron.get("hour"), hour), (cron.get("daymonth"), daymonth), (cron.get("month"), month), (cron.get("dayweek"), dayweek), (cron.get("spec"), None), ] if cid or identifier: tests.append((cron["cmd"], cmd)) if any([_needs_change(x, y) for x, y in tests]): if "spec" in cron: rm_special(user, cmd, identifier=cid) else: rm_job(user, cmd, identifier=cid) # Use old values when setting the new job if there was no # change needed for a given parameter if not _needs_change(cron.get("minute"), minute): minute = cron.get("minute") if not _needs_change(cron.get("hour"), hour): hour = cron.get("hour") if not _needs_change(cron.get("daymonth"), daymonth): daymonth = cron.get("daymonth") if not _needs_change(cron.get("month"), month): month = cron.get("month") if not _needs_change(cron.get("dayweek"), dayweek): dayweek = cron.get("dayweek") if not _needs_change(cron["commented"], commented): commented = cron["commented"] if not _needs_change(cron["comment"], comment): comment = cron["comment"] if not _needs_change(cron["cmd"], cmd): cmd = cron["cmd"] if cid == SALT_CRON_NO_IDENTIFIER: if identifier: cid = identifier if ( cid == SALT_CRON_NO_IDENTIFIER and cron["identifier"] is None ): cid = None cron["identifier"] = cid if not cid or (cid and not _needs_change(cid, identifier)): identifier = cid jret = set_job( user, minute, hour, daymonth, month, dayweek, cmd, commented=commented, comment=comment, identifier=identifier, ) if jret == "new": return "updated" else: return jret return "present" cron = { "cmd": cmd, "identifier": identifier, "comment": comment, "commented": commented, } cron.update( _get_cron_date_time( minute=minute, hour=hour, daymonth=daymonth, month=month, dayweek=dayweek ) ) lst["crons"].append(cron) comdat = _write_cron_lines(user, _render_tab(lst)) if comdat["retcode"]: # Failed to commit, return the error return comdat["stderr"] return "new" def rm_special(user, cmd, special=None, identifier=None): """ Remove a special cron job for a specified user. CLI Example: .. code-block:: bash salt '*' cron.rm_special root /usr/bin/foo """ lst = list_tab(user) ret = "absent" rm_ = None for ind, val in enumerate(lst["special"]): if rm_ is not None: break if _cron_matched(val, cmd, identifier=identifier): if special is None: # No special param was specified rm_ = ind else: if val["spec"] == special: rm_ = ind if rm_ is not None: lst["special"].pop(rm_) ret = "removed" comdat = _write_cron_lines(user, _render_tab(lst)) if comdat["retcode"]: # Failed to commit, return the error return comdat["stderr"] return ret def rm_job( user, cmd, minute=None, hour=None, daymonth=None, month=None, dayweek=None, identifier=None, ): """ Remove a cron job for a specified user. If any of the day/time params are specified, the job will only be removed if the specified params match. CLI Example: .. code-block:: bash salt '*' cron.rm_job root /usr/local/weekly salt '*' cron.rm_job root /usr/bin/foo dayweek=1 """ lst = list_tab(user) ret = "absent" rm_ = None for ind, val in enumerate(lst["crons"]): if rm_ is not None: break if _cron_matched(val, cmd, identifier=identifier): if not any( [x is not None for x in (minute, hour, daymonth, month, dayweek)] ): # No date/time params were specified rm_ = ind else: if _date_time_match( val, minute=minute, hour=hour, daymonth=daymonth, month=month, dayweek=dayweek, ): rm_ = ind if rm_ is not None: lst["crons"].pop(rm_) ret = "removed" comdat = _write_cron_lines(user, _render_tab(lst)) if comdat["retcode"]: # Failed to commit, return the error return comdat["stderr"] return ret rm = salt.utils.functools.alias_function(rm_job, "rm") def set_env(user, name, value=None): """ Set up an environment variable in the crontab. CLI Example: .. code-block:: bash salt '*' cron.set_env root MAILTO user@example.com """ lst = list_tab(user) for env in lst["env"]: if name == env["name"]: if value != env["value"]: rm_env(user, name) jret = set_env(user, name, value) if jret == "new": return "updated" else: return jret return "present" env = {"name": name, "value": value} lst["env"].append(env) comdat = _write_cron_lines(user, _render_tab(lst)) if comdat["retcode"]: # Failed to commit, return the error return comdat["stderr"] return "new" def rm_env(user, name): """ Remove cron environment variable for a specified user. CLI Example: .. code-block:: bash salt '*' cron.rm_env root MAILTO """ lst = list_tab(user) ret = "absent" rm_ = None for ind, val in enumerate(lst["env"]): if name == val["name"]: rm_ = ind if rm_ is not None: lst["env"].pop(rm_) ret = "removed" comdat = _write_cron_lines(user, _render_tab(lst)) if comdat["retcode"]: # Failed to commit, return the error return comdat["stderr"] return ret