def list_tab(user): ''' Return the contents of the specified user's incrontab CLI Example: .. code-block:: bash salt '*' incron.list_tab root ''' if user == 'system': data = raw_system_incron() else: data = raw_incron(user) log.debug("user data {0}".format(data)) ret = {'crons': [], 'pre': []} flag = False for line in data.splitlines(): if len(line.split()) > 3: # Appears to be a standard incron line comps = line.split() path = comps[0] mask = comps[1] cmd = ' '.join(comps[2:]) dat = {'path': path, 'mask': mask, 'cmd': cmd} ret['crons'].append(dat) else: ret['pre'].append(line) return ret
def list_tab(user): """ Return the contents of the specified user's incrontab CLI Example: .. code-block:: bash salt '*' incron.list_tab root """ if user == "system": data = raw_system_incron() else: data = raw_incron(user) log.debug("user data %s", data) ret = {"crons": [], "pre": []} flag = False for line in data.splitlines(): if len(line.split()) > 3: # Appears to be a standard incron line comps = line.split() path = comps[0] mask = comps[1] cmd = " ".join(comps[2:]) dat = {"path": path, "mask": mask, "cmd": cmd} ret["crons"].append(dat) else: ret["pre"].append(line) return ret
def list_tab(user): ''' Return the contents of the specified user's incrontab CLI Example: .. code-block:: bash salt '*' incron.list_tab root ''' if user == 'system': data = raw_system_incron() else: data = raw_incron(user) log.debug('incron user data %s', data) ret = {'crons': [], 'pre': []} flag = False comment = None tag = '# Line managed by Salt, do not edit' for line in data.splitlines(): if line.endswith(tag): if len(line.split()) > 3: # Appears to be a standard incron line comps = line.split() path = comps[0] mask = comps[1] (cmd, comment) = ' '.join(comps[2:]).split(' # ') dat = { 'path': path, 'mask': mask, 'cmd': cmd, 'comment': comment } ret['crons'].append(dat) comment = None else: ret['pre'].append(line) return ret
def _format_host(host, data, indent_level=1): """ Main highstate formatter. can be called recursively if a nested highstate contains other highstates (ie in an orchestration) """ host = salt.utils.data.decode(host) colors = salt.utils.color.get_colors(__opts__.get("color"), __opts__.get("color_theme")) tabular = __opts__.get("state_tabular", False) rcounts = {} rdurations = [] hcolor = colors["GREEN"] hstrs = [] nchanges = 0 strip_colors = __opts__.get("strip_colors", True) if isinstance(data, int): nchanges = 1 hstrs.append("{0} {1}{2[ENDC]}".format(hcolor, data, colors)) hcolor = colors["CYAN"] # Print the minion name in cyan elif isinstance(data, str): # Data in this format is from saltmod.function, # so it is always a 'change' nchanges = 1 for data in data.splitlines(): hstrs.append("{0} {1}{2[ENDC]}".format(hcolor, data, colors)) hcolor = colors["CYAN"] # Print the minion name in cyan elif isinstance(data, list): # Errors have been detected, list them in RED! hcolor = colors["LIGHT_RED"] hstrs.append(" {0}Data failed to compile:{1[ENDC]}".format( hcolor, colors)) for err in data: if strip_colors: err = salt.output.strip_esc_sequence( salt.utils.data.decode(err)) hstrs.append("{0}----------\n {1}{2[ENDC]}".format( hcolor, err, colors)) elif isinstance(data, dict): # Verify that the needed data is present data_tmp = {} for tname, info in data.items(): if (isinstance(info, dict) and tname != "changes" and info and "__run_num__" not in info): err = ("The State execution failed to record the order " "in which all states were executed. The state " "return missing data is:") hstrs.insert(0, pprint.pformat(info)) hstrs.insert(0, err) if isinstance(info, dict) and "result" in info: data_tmp[tname] = info data = data_tmp # Everything rendered as it should display the output for tname in sorted(data, key=lambda k: data[k].get("__run_num__", 0)): ret = data[tname] # Increment result counts rcounts.setdefault(ret["result"], 0) # unpack state compression counts compressed_count = 1 if (__opts__.get("state_compress_ids", False) and "_|-state_compressed_" in tname): _, _id, _, _ = tname.split("_|-") count_match = re.search(r"\((\d+)\)$", _id) if count_match: compressed_count = int(count_match.group(1)) rcounts[ret["result"]] += compressed_count rduration = ret.get("duration", 0) try: rdurations.append(float(rduration)) except ValueError: rduration, _, _ = rduration.partition(" ms") try: rdurations.append(float(rduration)) except ValueError: log.error("Cannot parse a float from duration %s", ret.get("duration", 0)) tcolor = colors["GREEN"] if ret.get("name") in [ "state.orch", "state.orchestrate", "state.sls" ]: nested = output(ret["changes"], indent_level=indent_level + 1) ctext = re.sub("^", " " * 14 * indent_level, "\n" + nested, flags=re.MULTILINE) schanged = True nchanges += 1 else: schanged, ctext = _format_changes(ret["changes"]) # if compressed, the changes are keyed by name if schanged and compressed_count > 1: nchanges += len(ret["changes"].get("compressed changes", {})) or 1 else: nchanges += 1 if schanged else 0 # Skip this state if it was successful & diff output was requested if (__opts__.get("state_output_diff", False) and ret["result"] and not schanged): continue # Skip this state if state_verbose is False, the result is True and # there were no changes made if (not __opts__.get("state_verbose", False) and ret["result"] and not schanged): continue if schanged: tcolor = colors["CYAN"] if ret["result"] is False: hcolor = colors["RED"] tcolor = colors["RED"] if ret["result"] is None: hcolor = colors["LIGHT_YELLOW"] tcolor = colors["LIGHT_YELLOW"] state_output = __opts__.get("state_output", "full").lower() comps = tname.split("_|-") if state_output.endswith("_id"): # Swap in the ID for the name. Refs #35137 comps[2] = comps[1] if state_output.startswith("filter"): # By default, full data is shown for all types. However, return # data may be excluded by setting state_output_exclude to a # comma-separated list of True, False or None, or including the # same list with the exclude option on the command line. For # now, this option must include a comma. For example: # exclude=True, # The same functionality is also available for making return # data terse, instead of excluding it. cliargs = __opts__.get("arg", []) clikwargs = {} for item in cliargs: if isinstance(item, dict) and "__kwarg__" in item: clikwargs = item.copy() exclude = clikwargs.get( "exclude", __opts__.get("state_output_exclude", [])) if isinstance(exclude, str): exclude = str(exclude).split(",") terse = clikwargs.get("terse", __opts__.get("state_output_terse", [])) if isinstance(terse, str): terse = str(terse).split(",") if str(ret["result"]) in terse: msg = _format_terse(tcolor, comps, ret, colors, tabular) hstrs.append(msg) continue if str(ret["result"]) in exclude: continue elif any(( state_output.startswith("terse"), state_output.startswith("mixed") and ret["result"] is not False, # only non-error'd state_output.startswith("changes") and ret["result"] and not schanged, # non-error'd non-changed )): # Print this chunk in a terse way and continue in the loop msg = _format_terse(tcolor, comps, ret, colors, tabular) hstrs.append(msg) continue state_lines = [ "{tcolor}----------{colors[ENDC]}", " {tcolor} ID: {comps[1]}{colors[ENDC]}", " {tcolor}Function: {comps[0]}.{comps[3]}{colors[ENDC]}", " {tcolor} Result: {ret[result]!s}{colors[ENDC]}", " {tcolor} Comment: {comment}{colors[ENDC]}", ] if __opts__.get("state_output_profile") and "start_time" in ret: state_lines.extend([ " {tcolor} Started: {ret[start_time]!s}{colors[ENDC]}", " {tcolor}Duration: {ret[duration]!s}{colors[ENDC]}", ]) # This isn't the prettiest way of doing this, but it's readable. if comps[1] != comps[2]: state_lines.insert( 3, " {tcolor} Name: {comps[2]}{colors[ENDC]}") # be sure that ret['comment'] is utf-8 friendly try: if not isinstance(ret["comment"], str): ret["comment"] = str(ret["comment"]) except UnicodeDecodeError: # If we got here, we're on Python 2 and ret['comment'] somehow # contained a str type with unicode content. ret["comment"] = salt.utils.stringutils.to_unicode( ret["comment"]) try: comment = salt.utils.data.decode(ret["comment"]) comment = comment.strip().replace("\n", "\n" + " " * 14) except AttributeError: # Assume comment is a list try: comment = ret["comment"].join(" ").replace( "\n", "\n" + " " * 13) except AttributeError: # Comment isn't a list either, just convert to string comment = str(ret["comment"]) comment = comment.strip().replace("\n", "\n" + " " * 14) # If there is a data attribute, append it to the comment if "data" in ret: if isinstance(ret["data"], list): for item in ret["data"]: comment = "{} {}".format(comment, item) elif isinstance(ret["data"], dict): for key, value in ret["data"].items(): comment = "{}\n\t\t{}: {}".format(comment, key, value) else: comment = "{} {}".format(comment, ret["data"]) for detail in ["start_time", "duration"]: ret.setdefault(detail, "") if ret["duration"] != "": ret["duration"] = "{} ms".format(ret["duration"]) svars = { "tcolor": tcolor, "comps": comps, "ret": ret, "comment": salt.utils.data.decode(comment), # This nukes any trailing \n and indents the others. "colors": colors, } hstrs.extend([sline.format(**svars) for sline in state_lines]) changes = " Changes: " + ctext hstrs.append("{0}{1}{2[ENDC]}".format(tcolor, changes, colors)) if "warnings" in ret: rcounts.setdefault("warnings", 0) rcounts["warnings"] += 1 wrapper = textwrap.TextWrapper(width=80, initial_indent=" " * 14, subsequent_indent=" " * 14) hstrs.append( " {colors[LIGHT_RED]} Warnings: {0}{colors[ENDC]}". format(wrapper.fill("\n".join(ret["warnings"])).lstrip(), colors=colors)) # Append result counts to end of output colorfmt = "{0}{1}{2[ENDC]}" rlabel = { True: "Succeeded", False: "Failed", None: "Not Run", "warnings": "Warnings", } count_max_len = max([len(str(x)) for x in rcounts.values()] or [0]) label_max_len = max([len(x) for x in rlabel.values()] or [0]) line_max_len = label_max_len + count_max_len + 2 # +2 for ': ' hstrs.append( colorfmt.format( colors["CYAN"], "\nSummary for {}\n{}".format(host, "-" * line_max_len), colors, )) def _counts(label, count): return "{0}: {1:>{2}}".format(label, count, line_max_len - (len(label) + 2)) # Successful states changestats = [] if None in rcounts and rcounts.get(None, 0) > 0: # test=True states changestats.append( colorfmt.format( colors["LIGHT_YELLOW"], "unchanged={}".format(rcounts.get(None, 0)), colors, )) if nchanges > 0: changestats.append( colorfmt.format(colors["GREEN"], "changed={}".format(nchanges), colors)) if changestats: changestats = " ({})".format(", ".join(changestats)) else: changestats = "" hstrs.append( colorfmt.format( colors["GREEN"], _counts(rlabel[True], rcounts.get(True, 0) + rcounts.get(None, 0)), colors, ) + changestats) # Failed states num_failed = rcounts.get(False, 0) hstrs.append( colorfmt.format( colors["RED"] if num_failed else colors["CYAN"], _counts(rlabel[False], num_failed), colors, )) if __opts__.get("state_output_pct", False): # Add success percentages to the summary output try: success_pct = round( ((rcounts.get(True, 0) + rcounts.get(None, 0)) / (sum(rcounts.values()) - rcounts.get("warnings", 0))) * 100, 2, ) hstrs.append( colorfmt.format( colors["GREEN"], _counts("Success %", success_pct), colors, )) except ZeroDivisionError: pass # Add failure percentages to the summary output try: failed_pct = round( (num_failed / (sum(rcounts.values()) - rcounts.get("warnings", 0))) * 100, 2, ) hstrs.append( colorfmt.format( colors["RED"] if num_failed else colors["CYAN"], _counts("Failure %", failed_pct), colors, )) except ZeroDivisionError: pass num_warnings = rcounts.get("warnings", 0) if num_warnings: hstrs.append( colorfmt.format( colors["LIGHT_RED"], _counts(rlabel["warnings"], num_warnings), colors, )) totals = "{0}\nTotal states run: {1:>{2}}".format( "-" * line_max_len, sum(rcounts.values()) - rcounts.get("warnings", 0), line_max_len - 7, ) hstrs.append(colorfmt.format(colors["CYAN"], totals, colors)) if __opts__.get("state_output_profile"): sum_duration = sum(rdurations) duration_unit = "ms" # convert to seconds if duration is 1000ms or more if sum_duration > 999: sum_duration /= 1000 duration_unit = "s" total_duration = "Total run time: {} {}".format( "{:.3f}".format(sum_duration).rjust(line_max_len - 5), duration_unit) hstrs.append( colorfmt.format(colors["CYAN"], total_duration, colors)) if strip_colors: host = salt.output.strip_esc_sequence(host) hstrs.insert(0, "{0}{1}:{2[ENDC]}".format(hcolor, host, colors)) return "\n".join(hstrs), nchanges > 0
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
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