PR#471 Rework build log display in web ui

Merges #471
https://pagure.io/koji/pull-request/471
This commit is contained in:
Mike McLean 2017-06-21 10:56:30 -04:00
commit e83e741618
4 changed files with 195 additions and 41 deletions

View file

@ -3538,6 +3538,35 @@ def get_build(buildInfo, strict=False):
return result
def get_build_logs(build):
"""Return a list of log files for the given build"""
buildinfo = get_build(build, strict=True)
logdir = koji.pathinfo.build_logs(buildinfo)
logreldir = koji.util.relpath(logdir, koji.pathinfo.topdir)
if not os.path.exists(logdir):
return []
if not os.path.isdir(logdir):
raise koji.GenericError("Not a directory: %s" % logdir)
logs = []
for dirpath, dirs, files in os.walk(logdir):
subdir = koji.util.relpath(dirpath, logdir)
for fn in files:
filepath = os.path.join(dirpath, fn)
if os.path.islink(filepath):
logger.warning("Symlink under logdir: %s", filepath)
continue
if not os.path.isfile(filepath):
logger.warning("Non-regular file under logdir: %s", filepath)
continue
loginfo = {
'name': fn,
'dir': subdir,
'path': "%s/%s/%s" % (logreldir, subdir, fn)
}
logs.append(loginfo)
return logs
def get_next_release(build_info):
"""find the last successful or deleted build of this N-V"""
values = {'name': build_info['name'],
@ -9273,6 +9302,7 @@ class RootExports(object):
listTags = staticmethod(list_tags)
getBuild = staticmethod(get_build)
getBuildLogs = staticmethod(get_build_logs)
getNextRelease = staticmethod(get_next_release)
getMavenBuild = staticmethod(get_maven_build)
getWinBuild = staticmethod(get_win_build)

View file

@ -0,0 +1,131 @@
import mock
import os
import shutil
import tempfile
import unittest
import koji
import kojihub
class TestGetBuildLogs(unittest.TestCase):
def setUp(self):
self.get_build = mock.patch('kojihub.get_build').start()
self.pathinfo = mock.patch('koji.pathinfo').start()
self.tempdir = tempfile.mkdtemp()
koji.pathinfo.build_logs.return_value = self.tempdir
def tearDown(self):
mock.patch.stopall()
shutil.rmtree(self.tempdir)
def make_tree(self, data):
for filepath in data:
path = "%s/%s" % (self.tempdir, filepath)
if path.endswith('/'):
# just make a directory
dirpath = path
path = None
else:
dirpath = os.path.dirname(path)
koji.ensuredir(dirpath)
if path:
with file(path, 'w') as fo:
fo.write('TEST LOG FILE CONTENTS\n')
def test_get_build_logs_basic(self):
files = [
'noarch/build.log',
'x86_64/build.log',
's390x/build.log',
]
files.sort()
self.make_tree(files)
data = kojihub.get_build_logs('fakebuild')
files2 = ["%s/%s" % (f['dir'], f['name']) for f in data]
files2.sort()
self.assertEqual(files, files2)
def test_get_build_logs_dir_missing(self):
koji.pathinfo.build_logs.return_value = "%s/NOSUCHDIR" % self.tempdir
data = kojihub.get_build_logs('fakebuild')
self.assertEqual(data, [])
def test_get_build_logs_notadir(self):
fn = "%s/SOMEFILE" % self.tempdir
with open(fn, 'w') as fo:
fo.write('NOT A DIRECTORY\n')
koji.pathinfo.build_logs.return_value = fn
try:
data = kojihub.get_build_logs('fakebuild')
raise Exception('Expected exception not raised')
except koji.GenericError as e:
self.assertEqual(e.args[0][:15], 'Not a directory')
def test_get_build_logs_emptydirs(self):
files = [
'./build.log',
'noarch/build.log',
'noarch/root.log',
'x86_64/build.log',
's390x/build.log',
'oddball/log/dir/fake.log',
]
empty_dirs = [
'foo/bar/baz/',
'a/b/c/',
'empty/',
]
files.sort()
self.make_tree(files + empty_dirs)
data = kojihub.get_build_logs('fakebuild')
files2 = ["%s/%s" % (f['dir'], f['name']) for f in data]
files2.sort()
self.assertEqual(files, files2)
@mock.patch('kojihub.logger')
def test_get_build_logs_symlinks(self, logger):
# symlinks should be ignored with a warning
files = [
'noarch/build.log',
'noarch/root.log',
'noarch/mock.log',
'noarch/checkout.log',
'noarch/readme.txt',
'oddball/log/dir/fake.log',
]
empty_dirs = [
'just_links/',
]
files.sort()
self.make_tree(files + empty_dirs)
os.symlink('SOME/PATH', '%s/%s' % (self.tempdir, 'symlink.log'))
os.symlink('SOME/PATH', '%s/%s' % (self.tempdir, 'just_links/foo.log'))
os.symlink('SOME/PATH', '%s/%s' % (self.tempdir, 'just_links/bar.log'))
data = kojihub.get_build_logs('fakebuild')
files2 = ["%s/%s" % (f['dir'], f['name']) for f in data]
files2.sort()
self.assertEqual(files, files2)
self.assertEqual(logger.warning.call_count, 3)
@mock.patch('kojihub.logger')
def test_get_build_logs_nonfile(self, logger):
# symlinks should be ignored
files = [
'noarch/build.log',
'noarch/root.log',
'noarch/mock.log',
'noarch/checkout.log',
'noarch/readme.txt',
'noarch/hello',
'oddball/log/dir/fake.log',
]
files.sort()
self.make_tree(files)
os.mkfifo('%s/%s' % (self.tempdir, 'this_is_a_named_pipe'))
data = kojihub.get_build_logs('fakebuild')
files2 = ["%s/%s" % (f['dir'], f['name']) for f in data]
files2.sort()
self.assertEqual(files, files2)
logger.warning.assert_called_once()

View file

@ -132,15 +132,6 @@
#end if
<tr>
<th>$arch</th>
<td>
#if $task
#if $arch == 'noarch'
(<a href="$nvrpath/data/logs/$noarch_log_dest/">build logs</a>)
#else
(<a href="$nvrpath/data/logs/$arch/">build logs</a>)
#end if
#end if
</td>
</tr>
#for $rpm in $rpmsByArch[$arch]
<tr>
@ -172,19 +163,6 @@
#for ext in $exts
<tr>
<th>$ext</th>
<td>
#if $task and $ext == $exts[0]
#if $btype == 'maven'
(<a href="$nvrpath/data/logs/maven/">build logs</a>)
#elif $btype == 'win'
(<a href="$nvrpath/data/logs/win/">build logs</a>)
#elif $btype == 'image'
(<a href="$nvrpath/data/logs/image">build logs</a>)
#else
(<a href="$nvrpath/data/logs">build logs</a>)
#end if
#end if
</td>
</tr>
#for $archive in $archivesByExt[$ext]
<tr>
@ -203,6 +181,29 @@
</td>
</tr>
#end for
#if $logs_by_dir
<tr>
<th>Logs</th>
<td class="container">
<table class="nested">
#set $logdirs = $logs_by_dir.keys()
#for logdir in $logdirs
<tr>
<th>$logdir</th>
</tr>
#for loginfo in $logs_by_dir[$logdir]
<tr>
<td/>
<td>
<a href="$loginfo.dl_url">$loginfo.name</a>
</td>
</tr>
#end for
#end for
</table>
</td>
</tr>
#end if
#if $changelog
<tr>
<th>Changelog</th>

View file

@ -1145,26 +1145,8 @@ def buildinfo(environ, buildID):
values['description'] = koji.fixEncoding(headers.get('description'))
values['changelog'] = server.getChangelogEntries(build['id'])
noarch_log_dest = 'noarch'
if build['task_id']:
task = server.getTaskInfo(build['task_id'], request=True)
if 'noarch' in rpmsByArch and \
[a for a in rpmsByArch.keys() if a not in ('noarch', 'src')]:
# This build has noarch and other-arch packages, indicating either
# noarch in extra-arches (kernel) or noarch subpackages.
# Point the log link to the arch of the buildArch task that the first
# noarch package came from. This will be correct in both the
# extra-arches case (noarch) and the subpackage case (one of the other
# arches). If noarch extra-arches and noarch subpackages are mixed in
# same build, this will become incorrect.
noarch_rpm = rpmsByArch['noarch'][0]
if noarch_rpm['buildroot_id']:
noarch_buildroot = server.getBuildroot(noarch_rpm['buildroot_id'])
if noarch_buildroot:
noarch_task = server.getTaskInfo(noarch_buildroot['task_id'], request=True)
if noarch_task:
noarch_log_dest = noarch_task['request'][2]
# get the summary, description, and changelogs from the built srpm
# if the build is not yet complete
if build['state'] != koji.BUILD_STATES['COMPLETE']:
@ -1189,6 +1171,17 @@ def buildinfo(environ, buildID):
else:
task = None
# get logs
logs = server.getBuildLogs(buildID)
logs_by_dir = {}
for loginfo in logs:
loginfo['dl_url'] = "%s/%s" % (topurl, loginfo['path'])
logdir = loginfo['dir']
if logdir == '.':
logdir = ''
logs_by_dir.setdefault(logdir, []).append(loginfo)
values['logs_by_dir'] = logs_by_dir
values['build'] = build
values['tags'] = tags
values['rpmsByArch'] = rpmsByArch
@ -1196,7 +1189,6 @@ def buildinfo(environ, buildID):
values['typeinfo'] = typeinfo
values['archiveIndex'] = archiveIndex
values['noarch_log_dest'] = noarch_log_dest
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else: