mirror of
https://github.com/ansible/awx.git
synced 2024-11-02 09:51:09 +03:00
Add new ansi_download format to download stdout and preserve ANSI escape sequences.
This commit is contained in:
parent
46922e956f
commit
b6e2e3da70
@ -80,3 +80,8 @@ class AnsiTextRenderer(PlainTextRenderer):
|
|||||||
|
|
||||||
media_type = 'text/plain'
|
media_type = 'text/plain'
|
||||||
format = 'ansi'
|
format = 'ansi'
|
||||||
|
|
||||||
|
|
||||||
|
class AnsiDownloadRenderer(PlainTextRenderer):
|
||||||
|
|
||||||
|
format = "ansi_download"
|
||||||
|
@ -13,6 +13,7 @@ Use the `format` query string parameter to specify the output format.
|
|||||||
* Plain Text with ANSI color codes: `?format=ansi`
|
* Plain Text with ANSI color codes: `?format=ansi`
|
||||||
* JSON structure: `?format=json`
|
* JSON structure: `?format=json`
|
||||||
* Downloaded Plain Text: `?format=txt_download`
|
* Downloaded Plain Text: `?format=txt_download`
|
||||||
|
* Downloaded Plain Text with ANSI color codes: `?format=ansi_download`
|
||||||
|
|
||||||
(_New in Ansible Tower 2.0.0_) When using the Browsable API, HTML and JSON
|
(_New in Ansible Tower 2.0.0_) When using the Browsable API, HTML and JSON
|
||||||
formats, the `start_line` and `end_line` query string parameters can be used
|
formats, the `start_line` and `end_line` query string parameters can be used
|
||||||
@ -21,7 +22,8 @@ to specify a range of line numbers to retrieve.
|
|||||||
Use `dark=1` or `dark=0` as a query string parameter to force or disable a
|
Use `dark=1` or `dark=0` as a query string parameter to force or disable a
|
||||||
dark background.
|
dark background.
|
||||||
|
|
||||||
+Files over {{ settings.STDOUT_MAX_BYTES_DISPLAY|filesizeformat }} (configurable) will not display in the browser. Use the `txt_download`
|
Files over {{ settings.STDOUT_MAX_BYTES_DISPLAY|filesizeformat }} (configurable)
|
||||||
+format to download the file directly to view it.
|
will not display in the browser. Use the `txt_download` or `ansi_download`
|
||||||
|
formats to download the file directly to view it.
|
||||||
|
|
||||||
{% include "api/_new_in_awx.md" %}
|
{% include "api/_new_in_awx.md" %}
|
||||||
|
@ -3897,20 +3897,47 @@ class UnifiedJobList(ListAPIView):
|
|||||||
new_in_148 = True
|
new_in_148 = True
|
||||||
|
|
||||||
|
|
||||||
|
class StdoutANSIFilter(object):
|
||||||
|
|
||||||
|
def __init__(self, fileobj):
|
||||||
|
self.fileobj = fileobj
|
||||||
|
self.extra_data = ''
|
||||||
|
if hasattr(fileobj,'close'):
|
||||||
|
self.close = fileobj.close
|
||||||
|
|
||||||
|
def read(self, size=-1):
|
||||||
|
data = self.extra_data
|
||||||
|
while size > 0 and len(data) < size:
|
||||||
|
line = self.fileobj.readline(size)
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
# Remove ANSI escape sequences used to embed event data.
|
||||||
|
line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line)
|
||||||
|
# Remove ANSI color escape sequences.
|
||||||
|
line = re.sub(r'\x1b[^m]*m', '', line)
|
||||||
|
data += line
|
||||||
|
if size > 0 and len(data) > size:
|
||||||
|
self.extra_data = data[size:]
|
||||||
|
data = data[:size]
|
||||||
|
else:
|
||||||
|
self.extra_data = ''
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJobStdout(RetrieveAPIView):
|
class UnifiedJobStdout(RetrieveAPIView):
|
||||||
|
|
||||||
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
|
||||||
serializer_class = UnifiedJobStdoutSerializer
|
serializer_class = UnifiedJobStdoutSerializer
|
||||||
renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer,
|
renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer,
|
||||||
PlainTextRenderer, AnsiTextRenderer,
|
PlainTextRenderer, AnsiTextRenderer,
|
||||||
renderers.JSONRenderer, DownloadTextRenderer]
|
renderers.JSONRenderer, DownloadTextRenderer, AnsiDownloadRenderer]
|
||||||
filter_backends = ()
|
filter_backends = ()
|
||||||
new_in_148 = True
|
new_in_148 = True
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
unified_job = self.get_object()
|
unified_job = self.get_object()
|
||||||
obj_size = unified_job.result_stdout_size
|
obj_size = unified_job.result_stdout_size
|
||||||
if request.accepted_renderer.format != 'txt_download' and obj_size > settings.STDOUT_MAX_BYTES_DISPLAY:
|
if request.accepted_renderer.format not in {'txt_download', 'ansi_download'} and obj_size > settings.STDOUT_MAX_BYTES_DISPLAY:
|
||||||
response_message = _("Standard Output too large to display (%(text_size)d bytes), "
|
response_message = _("Standard Output too large to display (%(text_size)d bytes), "
|
||||||
"only download supported for sizes over %(supported_size)d bytes") % {
|
"only download supported for sizes over %(supported_size)d bytes") % {
|
||||||
'text_size': obj_size, 'supported_size': settings.STDOUT_MAX_BYTES_DISPLAY}
|
'text_size': obj_size, 'supported_size': settings.STDOUT_MAX_BYTES_DISPLAY}
|
||||||
@ -3951,18 +3978,24 @@ class UnifiedJobStdout(RetrieveAPIView):
|
|||||||
elif content_format == 'html':
|
elif content_format == 'html':
|
||||||
return Response({'range': {'start': start, 'end': end, 'absolute_end': absolute_end}, 'content': body})
|
return Response({'range': {'start': start, 'end': end, 'absolute_end': absolute_end}, 'content': body})
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
elif request.accepted_renderer.format == 'txt':
|
||||||
|
return Response(unified_job.result_stdout)
|
||||||
elif request.accepted_renderer.format == 'ansi':
|
elif request.accepted_renderer.format == 'ansi':
|
||||||
return Response(unified_job.result_stdout_raw)
|
return Response(unified_job.result_stdout_raw)
|
||||||
elif request.accepted_renderer.format == 'txt_download':
|
elif request.accepted_renderer.format in {'txt_download', 'ansi_download'}:
|
||||||
try:
|
try:
|
||||||
content_fd = open(unified_job.result_stdout_file, 'r')
|
content_fd = open(unified_job.result_stdout_file, 'r')
|
||||||
|
if request.accepted_renderer.format == 'txt_download':
|
||||||
|
# For txt downloads, filter out ANSI escape sequences.
|
||||||
|
content_fd = StdoutANSIFilter(content_fd)
|
||||||
|
suffix = ''
|
||||||
|
else:
|
||||||
|
suffix = '_ansi'
|
||||||
response = HttpResponse(FileWrapper(content_fd), content_type='text/plain')
|
response = HttpResponse(FileWrapper(content_fd), content_type='text/plain')
|
||||||
response["Content-Disposition"] = 'attachment; filename="job_%s.txt"' % str(unified_job.id)
|
response["Content-Disposition"] = 'attachment; filename="job_%s%s.txt"' % (str(unified_job.id), suffix)
|
||||||
return response
|
return response
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response({"error": _("Error generating stdout download file: %s") % str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": _("Error generating stdout download file: %s") % str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
elif request.accepted_renderer.format == 'txt':
|
|
||||||
return Response(unified_job.result_stdout)
|
|
||||||
else:
|
else:
|
||||||
return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs)
|
return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user