1""" 2Used to manage the outputter system. This package is the modular system used 3for managing outputters. 4""" 5 6 7import errno 8import io 9import logging 10import os 11import re 12import sys 13import traceback 14 15import salt.loader 16import salt.utils.files 17import salt.utils.platform 18import salt.utils.stringutils 19 20# Are you really sure !!! 21# dealing with unicode is not as simple as setting defaultencoding 22# which can break other python modules imported by salt in bad ways... 23# reloading sys is not either a good idea... 24# reload(sys) 25# sys.setdefaultencoding('utf-8') 26 27log = logging.getLogger(__name__) 28 29 30def try_printout(data, out, opts, **kwargs): 31 """ 32 Safely get the string to print out, try the configured outputter, then 33 fall back to nested and then to raw 34 """ 35 try: 36 printout = get_printout(out, opts)(data, **kwargs) 37 if printout is not None: 38 return printout.rstrip() 39 except (KeyError, AttributeError, TypeError): 40 log.debug(traceback.format_exc()) 41 try: 42 printout = get_printout("nested", opts)(data, **kwargs) 43 if printout is not None: 44 return printout.rstrip() 45 except (KeyError, AttributeError, TypeError): 46 log.error("Nested output failed: ", exc_info=True) 47 printout = get_printout("raw", opts)(data, **kwargs) 48 if printout is not None: 49 return printout.rstrip() 50 51 52def get_progress(opts, out, progress): 53 """ 54 Get the progress bar from the given outputter 55 """ 56 return salt.loader.raw_mod(opts, out, "rawmodule", mod="output")[ 57 "{}.progress_iter".format(out) 58 ](progress) 59 60 61def update_progress(opts, progress, progress_iter, out): 62 """ 63 Update the progress iterator for the given outputter 64 """ 65 # Look up the outputter 66 try: 67 progress_outputter = salt.loader.outputters(opts)[out] 68 except KeyError: # Outputter is not loaded 69 log.warning("Progress outputter not available.") 70 return False 71 progress_outputter(progress, progress_iter) 72 73 74def progress_end(progress_iter): 75 try: 76 progress_iter.stop() 77 except Exception: # pylint: disable=broad-except 78 pass 79 return None 80 81 82def display_output(data, out=None, opts=None, **kwargs): 83 """ 84 Print the passed data using the desired output 85 """ 86 if opts is None: 87 opts = {} 88 display_data = try_printout(data, out, opts, **kwargs) 89 90 output_filename = opts.get("output_file", None) 91 log.trace("data = %s", data) 92 try: 93 # output filename can be either '' or None 94 if output_filename: 95 if not hasattr(output_filename, "write"): 96 # pylint: disable=resource-leakage 97 ofh = salt.utils.files.fopen(output_filename, "a") 98 # pylint: enable=resource-leakage 99 fh_opened = True 100 else: 101 # Filehandle/file-like object 102 ofh = output_filename 103 fh_opened = False 104 105 try: 106 fdata = display_data 107 if isinstance(fdata, str): 108 try: 109 fdata = fdata.encode("utf-8") 110 except (UnicodeDecodeError, UnicodeEncodeError): 111 # try to let the stream write 112 # even if we didn't encode it 113 pass 114 if fdata: 115 ofh.write(salt.utils.stringutils.to_str(fdata)) 116 ofh.write("\n") 117 finally: 118 if fh_opened: 119 ofh.close() 120 return 121 if display_data: 122 salt.utils.stringutils.print_cli(display_data) 123 except OSError as exc: 124 # Only raise if it's NOT a broken pipe 125 if exc.errno != errno.EPIPE: 126 raise 127 128 129def get_printout(out, opts=None, **kwargs): 130 """ 131 Return a printer function 132 """ 133 if opts is None: 134 opts = {} 135 136 if "output" in opts and opts["output"] != "highstate": 137 # new --out option, but don't choke when using --out=highstate at CLI 138 # See Issue #29796 for more information. 139 out = opts["output"] 140 141 # Handle setting the output when --static is passed. 142 if not out and opts.get("static"): 143 if opts.get("output"): 144 out = opts["output"] 145 elif opts.get("fun", "").split(".")[0] == "state": 146 # --static doesn't have an output set at this point, but if we're 147 # running a state function and "out" hasn't already been set, we 148 # should set the out variable to "highstate". Otherwise state runs 149 # are set to "nested" below. See Issue #44556 for more information. 150 out = "highstate" 151 152 if out == "text": 153 out = "txt" 154 elif out is None or out == "": 155 out = "nested" 156 if opts.get("progress", False): 157 out = "progress" 158 159 opts.update(kwargs) 160 if "color" not in opts: 161 162 def is_pipe(): 163 """ 164 Check if sys.stdout is a pipe or not 165 """ 166 try: 167 fileno = sys.stdout.fileno() 168 except (AttributeError, io.UnsupportedOperation): 169 fileno = -1 # sys.stdout is StringIO or fake 170 return not os.isatty(fileno) 171 172 if opts.get("force_color", False): 173 opts["color"] = True 174 elif ( 175 opts.get("no_color", False) or is_pipe() or salt.utils.platform.is_windows() 176 ): 177 opts["color"] = False 178 else: 179 opts["color"] = True 180 else: 181 if opts.get("force_color", False): 182 opts["color"] = True 183 elif opts.get("no_color", False) or salt.utils.platform.is_windows(): 184 opts["color"] = False 185 else: 186 pass 187 188 outputters = salt.loader.outputters(opts) 189 if out not in outputters: 190 # Since the grains outputter was removed we don't need to fire this 191 # error when old minions are asking for it 192 if out != "grains": 193 log.error( 194 "Invalid outputter %s specified, fall back to nested", 195 out, 196 ) 197 return outputters["nested"] 198 return outputters[out] 199 200 201def out_format(data, out, opts=None, **kwargs): 202 """ 203 Return the formatted outputter string for the passed data 204 """ 205 return try_printout(data, out, opts, **kwargs) 206 207 208def string_format(data, out, opts=None, **kwargs): 209 """ 210 Return the formatted outputter string, removing the ANSI escape sequences. 211 """ 212 raw_output = try_printout(data, out, opts, **kwargs) 213 ansi_escape = re.compile(r"\x1b[^m]*m") 214 return ansi_escape.sub("", raw_output) 215 216 217def html_format(data, out, opts=None, **kwargs): 218 """ 219 Return the formatted string as HTML. 220 """ 221 ansi_escaped_string = string_format(data, out, opts, **kwargs) 222 return ansi_escaped_string.replace(" ", " ").replace("\n", "<br />") 223 224 225def strip_esc_sequence(txt): 226 """ 227 Replace ESC (ASCII 27/Oct 33) to prevent unsafe strings 228 from writing their own terminal manipulation commands 229 """ 230 if isinstance(txt, str): 231 return txt.replace("\033", "?") 232 else: 233 return txt 234