mirror of
git://git.yoctoproject.org/yocto-autobuilder2.git
synced 2025-07-19 20:59:02 +02:00

This ensures one entry is shown per step with the logs on the same line. The logs are shown with the log names and the step name is only shown once. This make the resulting wiki entry much easier to use. Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
271 lines
9.9 KiB
Python
271 lines
9.9 KiB
Python
from buildbot.reporters import utils
|
|
from buildbot.util import service
|
|
from twisted.internet import defer
|
|
from twisted.python import log
|
|
from buildbot.process.results import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, CANCELLED
|
|
|
|
from yoctoabb.lib.wiki import YPWiki
|
|
|
|
import time
|
|
import pprint
|
|
import re
|
|
|
|
class WikiLog(service.BuildbotService):
|
|
name = "WikiLog"
|
|
wiki = None
|
|
# wantPreviousBuilds wantLogs
|
|
neededDetails = dict(wantProperties=True, wantSteps=True)
|
|
|
|
def checkConfig(self, wiki_uri, wiki_un, wiki_pass, wiki_page,
|
|
identifier=None, **kwargs):
|
|
service.BuildbotService.checkConfig(self)
|
|
|
|
@defer.inlineCallbacks
|
|
def reconfigService(self, wiki_uri, wiki_un, wiki_pass, wiki_page,
|
|
identifier=None, **kwargs):
|
|
yield service.BuildbotService.reconfigService(self)
|
|
self.wiki_page = wiki_page
|
|
self.identifier = None
|
|
self.idstring = ""
|
|
if identifier:
|
|
self.identifier = identifier.replace(" ", "-")
|
|
self.idstring = " on " + self.identifier
|
|
self.wiki = YPWiki(wiki_uri, wiki_un, wiki_pass)
|
|
|
|
@defer.inlineCallbacks
|
|
def startService(self):
|
|
yield service.BuildbotService.startService(self)
|
|
|
|
startConsuming = self.master.mq.startConsuming
|
|
self._buildCompleteConsumer = yield startConsuming(
|
|
self.buildFinished,
|
|
('builds', None, 'finished'))
|
|
|
|
self._buildStartedConsumer = yield startConsuming(
|
|
self.buildStarted,
|
|
('builds', None, 'new'))
|
|
|
|
def stopService(self):
|
|
self._buildCompleteConsumer.stopConsuming()
|
|
self._buildStartedConsumer.stopConsuming()
|
|
|
|
@defer.inlineCallbacks
|
|
def buildStarted(self, key, build):
|
|
yield utils.getDetailsForBuild(self.master, build, **self.neededDetails)
|
|
#log.err("wkl: buildStarted %s %s" % (key, pprint.pformat(build)))
|
|
|
|
# Only place initial entries in the wiki for builds with no parents
|
|
if not build['buildset']['parent_buildid']:
|
|
if not self.logBuild(build):
|
|
log.err("wkl: Failed to log build %s on %s" % (
|
|
build['buildid'], build['builder']['name']))
|
|
|
|
# Assume we only have a parent, doesn't handle builds nested more than one level.
|
|
@defer.inlineCallbacks
|
|
def buildFinished(self, key, build):
|
|
yield utils.getDetailsForBuild(self.master, build, **self.neededDetails)
|
|
#log.err("wkl: buildFinished %s %s" % (key, pprint.pformat(build)))
|
|
|
|
parent = None
|
|
if build['buildset']['parent_buildid']:
|
|
parent = yield self.master.data.get(("builds", build['buildset']['parent_buildid']))
|
|
yield utils.getDetailsForBuild(self.master, parent, **self.neededDetails)
|
|
|
|
if not self.updateBuild(build, parent):
|
|
log.err("wkl: Failed to update wikilog with build %s failure" %
|
|
build['buildid'])
|
|
|
|
def logBuild(self, build):
|
|
"""
|
|
Extract information about 'build' and post an entry to the wiki
|
|
|
|
@type build: buildbot.status.build.BuildStatus
|
|
"""
|
|
|
|
log.err("wkl: logbuild %s" % (build))
|
|
|
|
builder = build['builder']['name']
|
|
reason = "No reason given"
|
|
if 'reason' in build['properties'] and build['properties']['reason'][0]:
|
|
reason = build['properties']['reason'][0]
|
|
buildid = build['buildid']
|
|
start = build['started_at']
|
|
url = build['url']
|
|
buildbranch = build['properties']['branch_poky'][0]
|
|
|
|
chash = build['properties']['commit_poky'][0]
|
|
if not chash or len(chash) < 1 or chash == "HEAD":
|
|
chash = "YP_CHASH"
|
|
|
|
forcedby = "Unknown"
|
|
if 'owner' in build['properties']:
|
|
forcedby = build['properties']['owner'][0]
|
|
starttime = start.ctime()
|
|
|
|
sectionfmt = '==[{} {} {} - {} {}{}]=='
|
|
section_title = sectionfmt.format(url, builder, buildid, buildbranch, chash, self.idstring)
|
|
summaryfmt = 'Adding new BuildLog entry for build %s (%s)'
|
|
summary = summaryfmt % (buildid, chash)
|
|
summary = summary + self.idstring
|
|
content = "* '''Build ID''' - %s" % chash
|
|
content = content + self.idstring
|
|
content = content + '\n* Started at: %s\n' % starttime
|
|
content = content + '* ' + forcedby + '\n* ' + reason + '\n'
|
|
new_entry = '{}\n{}\n'.format(section_title, content)
|
|
|
|
blurb, entries = self.wiki.get_content(self.wiki_page)
|
|
if not blurb:
|
|
log.err("wkl: Unexpected content retrieved from wiki!")
|
|
return False
|
|
|
|
entries = new_entry + entries
|
|
cookies = self.wiki.login()
|
|
|
|
if not cookies:
|
|
log.err("wkl: Failed to login to wiki")
|
|
return False
|
|
|
|
if not self.wiki.post_entry(self.wiki_page, blurb+entries,
|
|
summary, cookies):
|
|
log.err("wkl: Failed to post entry for %s" % buildid)
|
|
return False
|
|
|
|
log.msg("wkl: Posting wikilog entry for %s" % buildid)
|
|
return True
|
|
|
|
def updateEntryBuildInfo(self, entry, title, build):
|
|
"""
|
|
Extract the branch and commit hash from the properties of the 'build'
|
|
and update the 'entry' string with extracted values
|
|
|
|
@type entry: string
|
|
@type build: buildbot.status.build.BuildStatus
|
|
"""
|
|
|
|
chash = None
|
|
if "yp_build_revision" in build['properties']:
|
|
chash = build['properties']['yp_build_revision'][0]
|
|
if not chash or len(chash) < 1 or chash == "HEAD":
|
|
chash = "YP_CHASH"
|
|
|
|
new_entry = entry.replace("YP_CHASH", chash, 2)
|
|
new_title = title.replace("YP_CHASH", chash, 2)
|
|
|
|
return new_entry, new_title
|
|
|
|
@defer.inlineCallbacks
|
|
def updateBuild(self, build, parent):
|
|
"""
|
|
Extract information about 'build' and update an entry in the wiki
|
|
|
|
@type build: buildbot.status.build.BuildStatus
|
|
"""
|
|
if not parent:
|
|
parent = build
|
|
|
|
url = build['url']
|
|
buildid = build['buildid']
|
|
builder = build['builder']['name']
|
|
log_entries = []
|
|
logentry = ""
|
|
for s in build['steps']:
|
|
|
|
# Ignore logs for steps which succeeded/cancelled
|
|
result = s['results']
|
|
if result in (SUCCESS, RETRY, CANCELLED):
|
|
continue
|
|
if result == WARNINGS:
|
|
# ignore warnings for log purposes for now
|
|
continue
|
|
|
|
# Log for FAILURE, SKIPPED, EXCEPTION
|
|
|
|
step_name = s['name']
|
|
step_number = s['number']
|
|
logs = yield self.master.data.get(("steps", s['stepid'], 'logs'))
|
|
logs = list(logs)
|
|
logstring = []
|
|
for l in logs:
|
|
log_url = '%s/steps/%s/logs/%s' % (url, step_number, l['name'])
|
|
logstring.append('[%s %s]' % (log_url, l['name']))
|
|
|
|
logs = ' '.join(logstring)
|
|
logentry = logentry + '\n* [%s %s] %s failed: %s\n' % (url, builder, step_name, logs)
|
|
|
|
blurb, entries = self.wiki.get_content(self.wiki_page)
|
|
if not blurb:
|
|
log.err("wkl: Unexpected content retrieved from wiki!")
|
|
return False
|
|
|
|
entry_list = re.split('\=\=\[(.+)\]\=\=', entries)
|
|
entry = ''
|
|
title = ''
|
|
foundmatch = False
|
|
# Start at the beginning of entry list and keep iterating until we find
|
|
# a title which contains our url/identifier
|
|
for idx, entry in enumerate(entry_list):
|
|
# The matched title contents should always start with a http*
|
|
# schemed URI
|
|
if entry.startswith('http'):
|
|
# format of the title is:
|
|
# ==[url builder buildid - buildbranch commit_hash on identifier]==
|
|
title_components = entry.split(None, 8)
|
|
|
|
if title_components[0] == parent['url']:
|
|
if self.identifier and title_components[7] == self.identifier:
|
|
foundmatch = True
|
|
elif not self.identifier:
|
|
foundmatch = True
|
|
|
|
if foundmatch:
|
|
entry = entry_list[idx+1]
|
|
title = entry_list[idx]
|
|
break
|
|
|
|
if not entry or not title:
|
|
errmsg = ("wkl: Failed to update entry for {0} couldn't find a matching title containing url: {1}")
|
|
log.err(errmsg.format(buildid, parent['url']))
|
|
return False
|
|
|
|
new_entry = '\n' + entry.strip() + logentry
|
|
|
|
summary = 'Updating entry with failures in %s' % builder
|
|
summary = summary + self.idstring
|
|
|
|
new_entry, new_title = self.updateEntryBuildInfo(new_entry, title, parent)
|
|
|
|
# If unchanged, skip the update
|
|
if entry == new_entry and title == new_title:
|
|
log.msg("wkl: Entry unchanged for wikilog entry %s" % buildid)
|
|
return True
|
|
|
|
# Find the point where the first entry's title starts and the second
|
|
# entry's title begins, then replace the text between those points
|
|
# with the newly generated entry.
|
|
it = re.finditer('\=\=\[(.+)\]\=\=', entries)
|
|
entry_title = next(it)
|
|
while entry_title.group(1) != title:
|
|
entry_title = next(it)
|
|
head = entries[:entry_title.start()]
|
|
try:
|
|
next_title = next(it)
|
|
tail = entries[next_title.start():]
|
|
except StopIteration:
|
|
# There was no following entry
|
|
tail = ""
|
|
|
|
update = head + "==[" + new_title + "]==\n" + new_entry + tail
|
|
|
|
cookies = self.wiki.login()
|
|
if not cookies:
|
|
log.err("wkl: Failed to login to wiki")
|
|
return False
|
|
|
|
if not self.wiki.post_entry(self.wiki_page, blurb+update, summary,
|
|
cookies):
|
|
log.err("wkl: Failed to update entry for %s" % buildid)
|
|
return False
|
|
|
|
log.msg("wkl: Updating wikilog entry for %s" % buildid)
|
|
return True
|