PR#471 Rework build log display in web ui
Merges #471 https://pagure.io/koji/pull-request/471
This commit is contained in:
commit
e83e741618
4 changed files with 195 additions and 41 deletions
|
|
@ -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)
|
||||
|
|
|
|||
131
tests/test_hub/test_get_build_logs.py
Normal file
131
tests/test_hub/test_get_build_logs.py
Normal 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()
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue