Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion runbot/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from babel.dates import LC_TIME, TIMEDELTA_UNITS, Locale
from collections import OrderedDict
from datetime import timedelta
from markupsafe import Markup
from markupsafe import Markup, escape

from odoo.fields import Domain
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, file_open, html_escape, OrderedSet
Expand Down Expand Up @@ -413,3 +413,46 @@ def test_tags_to_search_domain(self, exclude_error_id=None):
search_domains.append(tag_domain)
search_domain = Domain.OR(search_domains)
return search_domain


ANSI_ESCAPE_RE = re.compile(r'\x1b\[([0-9;]*)m')

ANSI_COLORS = {
30: '#000000', 31: '#AA0000', 32: '#00AA00', 33: '#AA5500',
34: '#0000AA', 35: '#AA00AA', 36: '#00AAAA', 37: '#AAAAAA',
90: '#555555', 91: '#FF5555', 92: '#55FF55', 93: '#FFFF55',
94: '#5555FF', 95: '#FF55FF', 96: '#55FFFF', 97: '#FFFFFF',
}


def ansi_to_html(text: str):
result = []
open_span = False
pos = 0

for match in ANSI_ESCAPE_RE.finditer(text):
chunk = text[pos:match.start()]
if chunk:
result.append(escape(chunk))

codes = match.group(1)
codes = [int(c) for c in codes.split(';') if c] if codes else [0]

for code in codes:
if open_span:
result.append(Markup('</span>'))
open_span = False
if code in ANSI_COLORS:
result.append(Markup(f'<span style="color:{ANSI_COLORS[code]}">'))
open_span = True

pos = match.end()

remaining = text[pos:]
if remaining:
result.append(escape(remaining))

if open_span:
result.append(Markup('</span>'))

return Markup('<span style="white-space:pre-wrap">') + Markup('').join(result) + Markup('</span>')
13 changes: 10 additions & 3 deletions runbot/models/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from odoo import api, exceptions, fields, models

from ..common import ansi_to_html
from ..container import docker_build
from ..fields import JsonDictField

Expand All @@ -20,6 +21,7 @@
USERGID = os.getgid()
USERNAME = getpass.getuser()


class DockerLayer(models.Model):
_name = 'runbot.docker_layer'
_inherit = 'mail.thread'
Expand Down Expand Up @@ -385,6 +387,7 @@ def _build(self, host=None):
content = self._get_cached_content(docker_build_path)
with open(self.env['runbot.runbot']._path('docker', tag_dir, 'Dockerfile'), 'w', encoding="utf-8") as Dockerfile:
Dockerfile.write(content)
self.env.cr.commit() # avoid to have a running transaction during the build
result = docker_build(docker_build_path, self.image_future_tag, self.pull_on_build, self.nocache)
duration = result['duration']
msg = result['msg']
Expand Down Expand Up @@ -434,7 +437,6 @@ def clean_output(output):
self.nocache = False
return image_id


class DockerBuildOutput(models.Model):
_name = 'runbot.docker_build_result'
_description = "Result of a docker file build"
Expand All @@ -445,9 +447,10 @@ class DockerBuildOutput(models.Model):
duration = fields.Float("Exec time")
dockerfile_id = fields.Many2one('runbot.dockerfile', string="Docker file")
output = fields.Text('Output')
formated_output = fields.Html('Colored Output', compute='_compute_formated_output')
content = fields.Text('Content')
identifier = fields.Char('Identifier')
summary = fields.Char("Summary", compute='_compute_summary', store=True)
summary = fields.Html("Summary", compute='_compute_summary', store=True)
metadata = JsonDictField("Metadata", help="Additionnal data about this image generated by nightly builds")

@api.depends('output')
Expand All @@ -458,7 +461,11 @@ def _compute_summary(self):
if len(line) > 5:
summary = line
break
record.summary = summary
record.summary = ansi_to_html(summary)

def _compute_formated_output(self):
for record in self:
record.formated_output = ansi_to_html(record.output)

def _getdocker_metadata_diff(self, other_build_result_id):
build_result_b = self.env['runbot.docker_build_result'].browse(other_build_result_id)
Expand Down
21 changes: 9 additions & 12 deletions runbot/models/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,19 +198,16 @@ def _docker_update_images(self):
docker_tag(dockerfile.image_previous_identifier, dockerfile.image_previous_tag)
docker_tag(dockerfile.image_identifier, dockerfile.image_tag)
docker_tag(dockerfile.image_future_identifier, dockerfile.image_future_tag)
for tag in [dockerfile.image_tag, dockerfile.image_future_tag]:
try:
docker_push(tag) # for now, always push locally
if is_main_registry:
docker_registry_url = self.docker_registry_url
if self.docker_registry_url:
docker_registry_url = self.docker_registry_url
else:
docker_registry_url = icp.get_param('runbot.docker_registry_url', default='').strip('/')
if docker_registry_url:
tags_to_push = [dockerfile.image_tag, dockerfile.image_future_tag]
docker_registry_url = self._get_docker_registry_url()
if docker_registry_url:
self.env.cr.commit()
for tag in tags_to_push:
try:
if is_main_registry or docker_registry_url == self.docker_registry_url:
docker_push(tag, docker_registry_url)
except ImageNotFound:
_logger.warning("Image tag `%s` not found. Skipping push", tag)
except ImageNotFound:
_logger.warning("Image tag `%s` not found. Skipping push", tag)
else:
if future_identifier:
docker_tag(future_identifier, dockerfile.image_tag) # for a setup without registry
Expand Down
6 changes: 5 additions & 1 deletion runbot/views/dockerfile_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@
<field name="summary"/>
</group>
<notebook>
<page string="Output">

<page string="Formated Output">
<field name="formated_output" readonly="1"/>
</page>
<page string="Raw Output">
<field name="output" readonly="1"/>
</page>
<page string="Content">
Expand Down