Merge pull request #79 from aplanas/master
New approach for action #1658 - move does not need a from argument
This commit is contained in:
commit
1b059751b5
124
osc-staging.py
124
osc-staging.py
@ -18,7 +18,6 @@ from osc.core import meta_get_packagelist
|
|||||||
from osc.core import http_GET
|
from osc.core import http_GET
|
||||||
from osc.core import http_POST
|
from osc.core import http_POST
|
||||||
from osc.core import server_diff
|
from osc.core import server_diff
|
||||||
from osc.core import store_read_project
|
|
||||||
|
|
||||||
# Expand sys.path to search modules inside the pluging directory
|
# Expand sys.path to search modules inside the pluging directory
|
||||||
_plugin_dir = os.path.expanduser('~/.osc-plugins')
|
_plugin_dir = os.path.expanduser('~/.osc-plugins')
|
||||||
@ -26,11 +25,12 @@ sys.path.append(_plugin_dir)
|
|||||||
from osclib.stagingapi import StagingAPI
|
from osclib.stagingapi import StagingAPI
|
||||||
from osclib.request_finder import RequestFinder
|
from osclib.request_finder import RequestFinder
|
||||||
|
|
||||||
OSC_STAGING_VERSION='0.0.1'
|
OSC_STAGING_VERSION = '0.0.1'
|
||||||
|
|
||||||
|
|
||||||
def _print_version(self):
|
def _print_version(self):
|
||||||
""" Print version information about this extension. """
|
""" Print version information about this extension. """
|
||||||
print('%s'%(self.OSC_STAGING_VERSION))
|
print(self.OSC_STAGING_VERSION)
|
||||||
quit(0)
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
@ -43,16 +43,24 @@ def _get_changed(opts, project, everything):
|
|||||||
f = http_GET(makeurl(opts.apiurl, ['source', project, pkg]))
|
f = http_GET(makeurl(opts.apiurl, ['source', project, pkg]))
|
||||||
linkinfo = ET.parse(f).getroot().find('linkinfo')
|
linkinfo = ET.parse(f).getroot().find('linkinfo')
|
||||||
if linkinfo is None:
|
if linkinfo is None:
|
||||||
ret.append({'pkg': pkg, 'code': 'NOT_LINK', 'msg': 'Not a source link'})
|
ret.append({'pkg': pkg, 'code': 'NOT_LINK',
|
||||||
|
'msg': 'Not a source link'})
|
||||||
continue
|
continue
|
||||||
if linkinfo.get('error'):
|
if linkinfo.get('error'):
|
||||||
ret.append({'pkg': pkg, 'code': 'BROKEN', 'msg': 'Broken source link'})
|
ret.append({'pkg': pkg, 'code': 'BROKEN',
|
||||||
|
'msg': 'Broken source link'})
|
||||||
continue
|
continue
|
||||||
t = linkinfo.get('project')
|
t = linkinfo.get('project')
|
||||||
p = linkinfo.get('package')
|
p = linkinfo.get('package')
|
||||||
r = linkinfo.get('revision')
|
r = linkinfo.get('revision')
|
||||||
if len(server_diff(opts.apiurl, t, p, r, project, pkg, None, True)) > 0:
|
if server_diff(opts.apiurl, t, p, r, project, pkg, None, True):
|
||||||
ret.append({'pkg': pkg, 'code': 'MODIFIED', 'msg': 'Has local modifications', 'pprj': t, 'ppkg': p})
|
ret.append({
|
||||||
|
'pkg': pkg,
|
||||||
|
'code': 'MODIFIED',
|
||||||
|
'msg': 'Has local modifications',
|
||||||
|
'pprj': t,
|
||||||
|
'ppkg': p
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -65,10 +73,10 @@ def _staging_remove(self, project, opts):
|
|||||||
"""
|
"""
|
||||||
chng = _get_changed(opts, project, True)
|
chng = _get_changed(opts, project, True)
|
||||||
if len(chng) > 0:
|
if len(chng) > 0:
|
||||||
print('Staging project "%s" is not clean:'%(project))
|
print('Staging project "%s" is not clean:' % project)
|
||||||
print('')
|
print('')
|
||||||
for pair in chng:
|
for pair in chng:
|
||||||
print(' * %s : %s'%(pair['pkg'],pair['msg']))
|
print(' * %s : %s' % (pair['pkg'], pair['msg']))
|
||||||
print('')
|
print('')
|
||||||
print('Really delete? (N/y)')
|
print('Really delete? (N/y)')
|
||||||
answer = sys.stdin.readline()
|
answer = sys.stdin.readline()
|
||||||
@ -82,7 +90,8 @@ def _staging_remove(self, project, opts):
|
|||||||
|
|
||||||
def _staging_submit_devel(self, project, opts):
|
def _staging_submit_devel(self, project, opts):
|
||||||
"""
|
"""
|
||||||
Generate new review requests for devel-projects based on our staging changes.
|
Generate new review requests for devel-projects based on our
|
||||||
|
staging changes.
|
||||||
:param project: staging project to submit into devel projects
|
:param project: staging project to submit into devel projects
|
||||||
"""
|
"""
|
||||||
chng = _get_changed(opts, project, True)
|
chng = _get_changed(opts, project, True)
|
||||||
@ -92,22 +101,23 @@ def _staging_submit_devel(self, project, opts):
|
|||||||
if len(chng) > 0:
|
if len(chng) > 0:
|
||||||
for pair in chng:
|
for pair in chng:
|
||||||
if pair['code'] != 'MODIFIED':
|
if pair['code'] != 'MODIFIED':
|
||||||
print('Error: Package "%s": %s'%(pair['pkg'],pair['msg']))
|
print('Error: Package "%s": %s' % (pair['pkg'], pair['msg']))
|
||||||
else:
|
else:
|
||||||
print('Sending changes back %s/%s -> %s/%s'%(project,pair['pkg'],pair['pprj'],pair['ppkg']))
|
print('Sending changes back %s/%s -> %s/%s' % (project, pair['pkg'], pair['pprj'], pair['ppkg']))
|
||||||
action_xml = '<request>';
|
action_xml = '<request>'
|
||||||
action_xml += ' <action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" />' % (project, pair['pkg'], pair['pprj'], pair['ppkg'])
|
action_xml += ' <action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" />' % (project, pair['pkg'], pair['pprj'], pair['ppkg'])
|
||||||
action_xml += ' </action>'
|
action_xml += ' </action>'
|
||||||
action_xml += ' <state name="new"/> <description>%s</description>' % msg
|
action_xml += ' <state name="new"/> <description>%s</description>' % msg
|
||||||
action_xml += '</request>'
|
action_xml += '</request>'
|
||||||
|
|
||||||
u = makeurl(opts.apiurl, ['request'], query='cmd=create&addrevision=1')
|
u = makeurl(opts.apiurl, ['request'],
|
||||||
|
query='cmd=create&addrevision=1')
|
||||||
f = http_POST(u, data=action_xml)
|
f = http_POST(u, data=action_xml)
|
||||||
|
|
||||||
root = ET.parse(f).getroot()
|
root = ET.parse(f).getroot()
|
||||||
print("Created request %s" % (root.get('id')))
|
print("Created request %s" % (root.get('id')))
|
||||||
else:
|
else:
|
||||||
print("No changes to submit")
|
print('No changes to submit')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +127,8 @@ def _staging_submit_devel(self, project, opts):
|
|||||||
help='manually specify different parent project during creation of staging')
|
help='manually specify different parent project during creation of staging')
|
||||||
@cmdln.option('-m', '--message', metavar='TEXT',
|
@cmdln.option('-m', '--message', metavar='TEXT',
|
||||||
help='manually specify different parent project during creation of staging')
|
help='manually specify different parent project during creation of staging')
|
||||||
|
@cmdln.option('-n', '--move', action='store_true',
|
||||||
|
help='force the selection to become a move')
|
||||||
@cmdln.option('-f', '--from', dest='from_', metavar='FROMPROJECT',
|
@cmdln.option('-f', '--from', dest='from_', metavar='FROMPROJECT',
|
||||||
help='manually specify different source project during request moving')
|
help='manually specify different source project during request moving')
|
||||||
@cmdln.option('-v', '--version', action='store_true',
|
@cmdln.option('-v', '--version', action='store_true',
|
||||||
@ -126,32 +138,33 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
|
|
||||||
"check" will check if all packages are links without changes
|
"check" will check if all packages are links without changes
|
||||||
|
|
||||||
"remove" (or "r") will delete the staging project into submit requests for openSUSE:Factory
|
"remove" (or "r") will delete the staging project into submit
|
||||||
|
requests for openSUSE:Factory
|
||||||
|
|
||||||
"submit-devel" (or "s") will create review requests for changed packages in staging project
|
"submit-devel" (or "s") will create review requests for changed
|
||||||
into their respective devel projects to obtain approval from maitnainers for pushing the
|
packages in staging project into their respective devel
|
||||||
|
projects to obtain approval from maitnainers for pushing the
|
||||||
changes to openSUSE:Factory
|
changes to openSUSE:Factory
|
||||||
|
|
||||||
"freeze" will freeze the sources of the project's links (not affecting the packages actually in)
|
"freeze" will freeze the sources of the project's links (not
|
||||||
|
affecting the packages actually in)
|
||||||
|
|
||||||
"accept" will accept all requests in openSUSE:Factory:Staging:<LETTER> (into Factory)
|
"accept" will accept all requests in
|
||||||
|
openSUSE:Factory:Staging:<LETTER> (into Factory)
|
||||||
|
|
||||||
"list" will pick the requests not in rings
|
"list" will pick the requests not in rings
|
||||||
|
|
||||||
"select" will add requests to the project
|
"select" will add requests to the project
|
||||||
"unselect" will remove them project - pushing them back to the backlog
|
"unselect" will remove them project - pushing them back to the backlog
|
||||||
|
|
||||||
"move" will move a list of REQUESTs from the current project to PROJECT
|
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
osc staging check [--everything] REPO
|
osc staging check [--everything] REPO
|
||||||
osc staging remove REPO
|
osc staging remove REPO
|
||||||
osc staging submit-devel [-m message] REPO
|
osc staging submit-devel [-m message] REPO
|
||||||
osc staging freeze PROJECT
|
osc staging freeze PROJECT
|
||||||
osc staging list
|
osc staging list
|
||||||
osc staging select LETTER REQUEST...
|
osc staging select [--move [-from PROJECT]] LETTER REQUEST...
|
||||||
osc staging unselect LETTER REQUEST...
|
osc staging unselect LETTER REQUEST...
|
||||||
osc staging move [--from PROJECT] PROJECT REQUEST...
|
|
||||||
osc staging accept LETTER
|
osc staging accept LETTER
|
||||||
osc staging cleanup_rings
|
osc staging cleanup_rings
|
||||||
"""
|
"""
|
||||||
@ -164,16 +177,14 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
cmd = args[0]
|
cmd = args[0]
|
||||||
if cmd in ['submit-devel', 's', 'remove', 'r', 'accept', 'freeze']:
|
if cmd in ['submit-devel', 's', 'remove', 'r', 'accept', 'freeze']:
|
||||||
min_args, max_args = 1, 1
|
min_args, max_args = 1, 1
|
||||||
elif cmd in ['check']:
|
elif cmd == 'check':
|
||||||
min_args, max_args = 0, 2
|
min_args, max_args = 0, 2
|
||||||
elif cmd in ['select', 'unselect']:
|
elif cmd in ['select', 'unselect']:
|
||||||
min_args, max_args = 2, None
|
min_args, max_args = 2, None
|
||||||
elif cmd in ['move']:
|
|
||||||
min_args, max_args = 2, None
|
|
||||||
elif cmd in ['list', 'cleanup_rings']:
|
elif cmd in ['list', 'cleanup_rings']:
|
||||||
min_args, max_args = 0, 0
|
min_args, max_args = 0, 0
|
||||||
else:
|
else:
|
||||||
raise oscerr.WrongArgs('Unknown command: %s'%(cmd))
|
raise oscerr.WrongArgs('Unknown command: %s' % cmd)
|
||||||
if len(args) - 1 < min_args:
|
if len(args) - 1 < min_args:
|
||||||
raise oscerr.WrongArgs('Too few arguments.')
|
raise oscerr.WrongArgs('Too few arguments.')
|
||||||
if not max_args is None and len(args) - 1 > max_args:
|
if not max_args is None and len(args) - 1 > max_args:
|
||||||
@ -190,15 +201,15 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
# FIXME: de-duplicate and use function when cleaning up this file
|
# FIXME: de-duplicate and use function when cleaning up this file
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
prj = api.prj_from_letter(args[1])
|
prj = api.prj_from_letter(args[1])
|
||||||
state = api.check_project_status(prj, True)
|
state = api.check_project_status(prj, True)
|
||||||
|
|
||||||
# If the state is green we do nothing
|
# If the state is green we do nothing
|
||||||
if not state:
|
if not state:
|
||||||
print('Skipping empty staging project: {0}'.format(prj))
|
print('Skipping empty staging project: {}'.format(prj))
|
||||||
print('')
|
print('')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
print('Checking staging project: {0}'.format(prj))
|
print('Checking staging project: {}'.format(prj))
|
||||||
if type(state) is list:
|
if type(state) is list:
|
||||||
print(' -- Project still neeeds attention')
|
print(' -- Project still neeeds attention')
|
||||||
for i in state:
|
for i in state:
|
||||||
@ -213,11 +224,11 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
|
|
||||||
# If the state is green we do nothing
|
# If the state is green we do nothing
|
||||||
if not state:
|
if not state:
|
||||||
print('Skipping empty staging project: {0}'.format(prj))
|
print('Skipping empty staging project: {}'.format(prj))
|
||||||
print('')
|
print('')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print('Checking staging project: {0}'.format(prj))
|
print('Checking staging project: {}'.format(prj))
|
||||||
if type(state) is list:
|
if type(state) is list:
|
||||||
print(' -- Project still neeeds attention')
|
print(' -- Project still neeeds attention')
|
||||||
for i in state:
|
for i in state:
|
||||||
@ -235,30 +246,41 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
elif cmd in ['freeze']:
|
elif cmd in ['freeze']:
|
||||||
import osclib.freeze_command
|
import osclib.freeze_command
|
||||||
for prj in args[1:]:
|
for prj in args[1:]:
|
||||||
osclib.freeze_command.FreezeCommand(api).perform(api.prj_from_letter(prj))
|
osclib.freeze_command.FreezeCommand(api).perform(api. prj_from_letter(prj))
|
||||||
elif cmd in ['accept']:
|
elif cmd in ['accept']:
|
||||||
import osclib.accept_command
|
import osclib.accept_command
|
||||||
osclib.accept_command.AcceptCommand(api).perform(api.prj_from_letter(args[1]))
|
osclib.accept_command.AcceptCommand(api).perform(api. prj_from_letter(args[1]))
|
||||||
elif cmd in ['select', 'unselect']:
|
elif cmd in ['select', 'unselect']:
|
||||||
stprj = api.prj_from_letter(args[1])
|
tprj = api.prj_from_letter(args[1])
|
||||||
if not api.prj_frozen_enough(stprj):
|
if not api.prj_frozen_enough(tprj):
|
||||||
print "Freeze the prj first"
|
print('Freeze the prj first')
|
||||||
return False
|
return False
|
||||||
for rq in RequestFinder.find_sr(args[2:], opts.apiurl):
|
for rq, rq_prj in RequestFinder.find_sr(args[2:], opts.apiurl).items():
|
||||||
if cmd == 'select':
|
if cmd == 'select' and 'staging' not in rq_prj:
|
||||||
api.rq_to_prj(rq, stprj)
|
# Normal 'select' command
|
||||||
else:
|
api.rq_to_prj(rq, tprj)
|
||||||
api.rm_from_prj(stprj, request_id=rq)
|
elif cmd == 'select' and 'staging' in rq_prj and opts.move:
|
||||||
|
# 'select' command becomes a 'move'
|
||||||
|
fprj = None
|
||||||
|
if opts.from_:
|
||||||
|
fprj = api.prj_from_letter(opts.from_)
|
||||||
|
else:
|
||||||
|
fprj = rq_prj['staging']
|
||||||
|
print('Moving "{}" from "{}" to "{}"'.format(rq, fprj, tprj))
|
||||||
|
api.move_between_project(fprj, rq, tprj)
|
||||||
|
elif cmd == 'select' and 'staging' in rq_prj and not opts.move:
|
||||||
|
# Previously selected, but not explicit move
|
||||||
|
msg = 'Request "{}" is actually in "{}".\n'
|
||||||
|
msg = msg.format(rq, rq_prj['staging'])
|
||||||
|
msg += 'Use --move (-n) modifier to move the request from "{}" to "{}"'
|
||||||
|
msg = msg.format(rq_prj['staging'], tprj)
|
||||||
|
print(msg)
|
||||||
|
elif cmd == 'unselect':
|
||||||
|
api.rm_from_prj(tprj, request_id=rq)
|
||||||
api.add_review(rq, by_group='factory-staging',
|
api.add_review(rq, by_group='factory-staging',
|
||||||
msg='Please recheck')
|
msg='Please recheck')
|
||||||
api.build_switch_prj(stprj, 'enable')
|
else:
|
||||||
|
raise oscerr.WrongArgs('Arguments for select are not correct.')
|
||||||
elif cmd in ['move']:
|
|
||||||
cprj = store_read_project(os.getcwd())
|
|
||||||
sprj = api.prj_from_letter(opts.from_) if opts.from_ else cprj
|
|
||||||
tprj = api.prj_from_letter(args[1])
|
|
||||||
for rq in RequestFinder.find_sr(args[2:], opts.apiurl):
|
|
||||||
api.move_between_project(sprj, rq, tprj)
|
|
||||||
elif cmd in ['cleanup_rings']:
|
elif cmd in ['cleanup_rings']:
|
||||||
import osclib.cleanup_rings
|
import osclib.cleanup_rings
|
||||||
osclib.cleanup_rings.CleanupRings(opts.apiurl).perform()
|
osclib.cleanup_rings.CleanupRings(opts.apiurl).perform()
|
||||||
|
@ -6,17 +6,39 @@ from osc.core import makeurl
|
|||||||
from osc.core import http_GET
|
from osc.core import http_GET
|
||||||
|
|
||||||
|
|
||||||
|
FACTORY = 'openSUSE:Factory'
|
||||||
|
STG_PREFIX = 'openSUSE:Factory:Staging:'
|
||||||
|
|
||||||
|
|
||||||
class RequestFinder:
|
class RequestFinder:
|
||||||
|
|
||||||
@classmethod
|
def __init__(self, apiurl):
|
||||||
def find_request_id(self, request, apiurl):
|
# Store the list of submit request together with the source project
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# {
|
||||||
|
# 212454: {
|
||||||
|
# 'project': 'openSUSE:Factory',
|
||||||
|
# },
|
||||||
|
#
|
||||||
|
# 223870: {
|
||||||
|
# 'project': 'openSUSE:Factory',
|
||||||
|
# 'staging': 'openSUSE:Factory:Staging:A',
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
self.apiurl = apiurl
|
||||||
|
self.srs = {}
|
||||||
|
|
||||||
|
def find_request_id(self, request):
|
||||||
"""
|
"""
|
||||||
Look up the request by ID to verify if it is correct
|
Look up the request by ID to verify if it is correct
|
||||||
:param request: ID of the added request
|
:param request: ID of the added request
|
||||||
:param apiurl: OBS url
|
:param apiurl: OBS url
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = makeurl(apiurl, ['request', str(request)])
|
url = makeurl(self.apiurl, ['request', str(request)])
|
||||||
try:
|
try:
|
||||||
f = http_GET(url)
|
f = http_GET(url)
|
||||||
except urllib2.HTTPError:
|
except urllib2.HTTPError:
|
||||||
@ -28,72 +50,100 @@ class RequestFinder:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
project = root.find('action').find('target').get('project')
|
project = root.find('action').find('target').get('project')
|
||||||
if project != 'openSUSE:Factory':
|
if project != FACTORY and not project.startswith(STG_PREFIX):
|
||||||
raise oscerr.WrongArgs('Request {} is not for openSUSE:Factory, but for {}'.format(request, project))
|
msg = 'Request {} is not for openSUSE:Factory, but for {}'
|
||||||
self.srids.add(int(request))
|
msg = msg.format(request, project)
|
||||||
|
raise oscerr.WrongArgs(msg)
|
||||||
|
self.srs[int(request)] = {'project': project}
|
||||||
|
|
||||||
|
for review in root.findall('review'):
|
||||||
|
if review.get('by_project'):
|
||||||
|
self.srs[int(request)]['staging'] = review.get('by_project')
|
||||||
|
break
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@classmethod
|
def find_request_package(self, package):
|
||||||
def find_request_package(self, package, apiurl):
|
|
||||||
"""
|
"""
|
||||||
Look up the package by its name and return the SR#
|
Look up the package by its name and return the SR#
|
||||||
:param package: name of the package
|
:param package: name of the package
|
||||||
:param apiurl: OBS url
|
:param apiurl: OBS url
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = makeurl(apiurl, ['request'], 'states=new,review,declined&project=openSUSE:Factory&view=collection&package={}'.format(package))
|
query = 'states=new,review,declined&project=openSUSE:Factory&view=collection&package={}'
|
||||||
|
query = query.format(package)
|
||||||
|
url = makeurl(self.apiurl, ['request'], query)
|
||||||
f = http_GET(url)
|
f = http_GET(url)
|
||||||
|
|
||||||
root = ET.parse(f).getroot()
|
root = ET.parse(f).getroot()
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
for x in root.findall('request'):
|
for sr in root.findall('request'):
|
||||||
# TODO: check the package matches - OBS is case insensitive
|
# TODO: check the package matches - OBS is case insensitive
|
||||||
self.srids.add(int(x.get('id')))
|
request = int(sr.get('id'))
|
||||||
|
self.srs[request] = {'project': 'openSUSE:Factory'}
|
||||||
|
for review in sr.findall('review'):
|
||||||
|
if review.get('by_project'):
|
||||||
|
self.srs[request]['staging'] = review.get('by_project')
|
||||||
|
break
|
||||||
if ret:
|
if ret:
|
||||||
raise oscerr.WrongArgs('There are multiple requests for package "{}": {}'.format(package, ', '.join(map(str, self.srids))))
|
msg = 'There are multiple requests for package "{}": {}'
|
||||||
|
msg = msg.format(package, ', '.join(map(str, self.srs.keys())))
|
||||||
|
raise oscerr.WrongArgs(msg)
|
||||||
ret = True
|
ret = True
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@classmethod
|
def find_request_project(self, source_project):
|
||||||
def find_request_project(self, source_project, apiurl):
|
|
||||||
"""
|
"""
|
||||||
Look up the source project by its name and return the SR#(s)
|
Look up the source project by its name and return the SR#(s)
|
||||||
:param source_project: name of the source project
|
:param source_project: name of the source project
|
||||||
:param apiurl: OBS url
|
:param apiurl: OBS url
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = makeurl(apiurl, ['request'], 'states=new,review&project=openSUSE:Factory&view=collection')
|
query = 'states=new,review&project=openSUSE:Factory&view=collection'
|
||||||
|
url = makeurl(self.apiurl, ['request'], query)
|
||||||
f = http_GET(url)
|
f = http_GET(url)
|
||||||
root = ET.parse(f).getroot()
|
root = ET.parse(f).getroot()
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
for rq in root.findall('request'):
|
for sr in root.findall('request'):
|
||||||
for a in rq.findall('action'):
|
for act in sr.findall('action'):
|
||||||
s = a.find('source')
|
src = act.find('source')
|
||||||
if s is not None and s.get('project') == source_project:
|
if src is not None and src.get('project') == source_project:
|
||||||
self.srids.add(int(rq.attrib['id']))
|
request = int(sr.attrib['id'])
|
||||||
|
self.srs[request] = {'project': 'openSUSE:Factory'}
|
||||||
|
for review in sr.findall('review'):
|
||||||
|
if review.get('by_project'):
|
||||||
|
self.srs[request]['staging'] = review.get('by_project')
|
||||||
|
break
|
||||||
ret = True
|
ret = True
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def find(self, pkgs):
|
||||||
|
"""
|
||||||
|
Search for all various mutations and return list of SR#s
|
||||||
|
:param pkgs: mesh of argumets to search for
|
||||||
|
|
||||||
|
This function is only called for its side effect.
|
||||||
|
"""
|
||||||
|
for p in pkgs:
|
||||||
|
if self.find_request_package(p):
|
||||||
|
continue
|
||||||
|
if self.find_request_id(p):
|
||||||
|
continue
|
||||||
|
if self.find_request_project(p):
|
||||||
|
continue
|
||||||
|
raise oscerr.WrongArgs('No SR# found for: {}'.format(p))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_sr(self, pkgs, apiurl):
|
def find_sr(cls, pkgs, apiurl):
|
||||||
"""
|
"""
|
||||||
Search for all various mutations and return list of SR#s
|
Search for all various mutations and return list of SR#s
|
||||||
:param pkgs: mesh of argumets to search for
|
:param pkgs: mesh of argumets to search for
|
||||||
:param apiurl: OBS url
|
:param apiurl: OBS url
|
||||||
"""
|
"""
|
||||||
|
finder = cls(apiurl)
|
||||||
self.srids = set()
|
finder.find(pkgs)
|
||||||
for p in pkgs:
|
return finder.srs
|
||||||
if self.find_request_package(p, apiurl):
|
|
||||||
continue
|
|
||||||
if self.find_request_id(p, apiurl):
|
|
||||||
continue
|
|
||||||
if self.find_request_project(p, apiurl):
|
|
||||||
continue
|
|
||||||
raise oscerr.WrongArgs('No SR# found for: {0}'.format(p))
|
|
||||||
|
|
||||||
# this is needed in order to ensure we have one level list not nested one
|
|
||||||
return sorted(list(self.srids))
|
|
||||||
|
@ -82,7 +82,8 @@ class StagingAPI(object):
|
|||||||
package_info['package'] = root.attrib['package']
|
package_info['package'] = root.attrib['package']
|
||||||
return package_info
|
return package_info
|
||||||
|
|
||||||
def move_between_project(self, source_project, req_id, destination_project):
|
def move_between_project(self, source_project, req_id,
|
||||||
|
destination_project):
|
||||||
"""
|
"""
|
||||||
Move selected package from one staging to another
|
Move selected package from one staging to another
|
||||||
:param source_project: Source project
|
:param source_project: Source project
|
||||||
@ -114,16 +115,16 @@ class StagingAPI(object):
|
|||||||
|
|
||||||
projects = []
|
projects = []
|
||||||
|
|
||||||
url = self.makeurl(['search', 'project',
|
query = "id?match=starts-with(@name,'openSUSE:Factory:Staging:')"
|
||||||
'id?match=starts-with(@name,\'openSUSE:Factory:Staging:\')'])
|
url = self.makeurl(['search', 'project', query])
|
||||||
projxml = http_GET(url)
|
projxml = http_GET(url)
|
||||||
root = ET.parse(projxml).getroot()
|
root = ET.parse(projxml).getroot()
|
||||||
for val in root.findall('project'):
|
for val in root.findall('project'):
|
||||||
projects.append(val.get('name'))
|
projects.append(val.get('name'))
|
||||||
return projects
|
return projects
|
||||||
|
|
||||||
def change_review_state(self, request_id, newstate, message=None, by_group=None,
|
def change_review_state(self, request_id, newstate, message=None,
|
||||||
by_user=None, by_project=None):
|
by_group=None, by_user=None, by_project=None):
|
||||||
"""
|
"""
|
||||||
Change review state of the staging request
|
Change review state of the staging request
|
||||||
:param request_id: id of the request
|
:param request_id: id of the request
|
||||||
@ -145,7 +146,8 @@ class StagingAPI(object):
|
|||||||
review.state == 'new':
|
review.state == 'new':
|
||||||
|
|
||||||
# call osc's function
|
# call osc's function
|
||||||
return change_review_state(self.apiurl, str(request_id), newstate,
|
return change_review_state(self.apiurl, str(request_id),
|
||||||
|
newstate,
|
||||||
by_group=by_group,
|
by_group=by_group,
|
||||||
by_user=by_user,
|
by_user=by_user,
|
||||||
by_project=by_project)
|
by_project=by_project)
|
||||||
@ -163,7 +165,8 @@ class StagingAPI(object):
|
|||||||
request_id = int(request.get('id'))
|
request_id = int(request.get('id'))
|
||||||
action = request.findall('action')
|
action = request.findall('action')
|
||||||
if not action:
|
if not action:
|
||||||
raise oscerr.WrongArgs('Request {} has no action'.format(request_id))
|
msg = 'Request {} has no action'.format(request_id)
|
||||||
|
raise oscerr.WrongArgs(msg)
|
||||||
# we care only about first action
|
# we care only about first action
|
||||||
action = action[0]
|
action = action[0]
|
||||||
|
|
||||||
@ -173,14 +176,17 @@ class StagingAPI(object):
|
|||||||
|
|
||||||
# If the values are empty it is no error
|
# If the values are empty it is no error
|
||||||
if not target_project or not target_package:
|
if not target_project or not target_package:
|
||||||
logging.info('no target/package in request {}, action {}; '.format(request_id, action))
|
msg = 'no target/package in request {}, action {}; '
|
||||||
|
msg = msg.format(request_id, action)
|
||||||
|
logging.info(msg)
|
||||||
|
|
||||||
# Verify the package ring
|
# Verify the package ring
|
||||||
ring = self.ring_packages.get(target_package, None)
|
ring = self.ring_packages.get(target_package, None)
|
||||||
if not ring:
|
if not ring:
|
||||||
# accept the request here
|
# accept the request here
|
||||||
message = "No need for staging, not in tested ring project."
|
message = 'No need for staging, not in tested ring project.'
|
||||||
self.change_review_state(request_id, 'accepted', message=message, by_group='factory-staging')
|
self.change_review_state(request_id, 'accepted', message=message,
|
||||||
|
by_group='factory-staging')
|
||||||
|
|
||||||
def get_open_requests(self):
|
def get_open_requests(self):
|
||||||
"""
|
"""
|
||||||
@ -194,7 +200,8 @@ class StagingAPI(object):
|
|||||||
# xpath query, using the -m, -r, -s options
|
# xpath query, using the -m, -r, -s options
|
||||||
where = "@by_group='factory-staging'+and+@state='new'"
|
where = "@by_group='factory-staging'+and+@state='new'"
|
||||||
|
|
||||||
url = self.makeurl(['search', 'request'], "match=state/@name='review'+and+review[%s]" % where)
|
query = "match=state/@name='review'+and+review[{}]".format(where)
|
||||||
|
url = self.makeurl(['search', 'request'], query)
|
||||||
f = http_GET(url)
|
f = http_GET(url)
|
||||||
root = ET.parse(f).getroot()
|
root = ET.parse(f).getroot()
|
||||||
|
|
||||||
@ -204,7 +211,9 @@ class StagingAPI(object):
|
|||||||
|
|
||||||
def dispatch_open_requests(self):
|
def dispatch_open_requests(self):
|
||||||
"""
|
"""
|
||||||
Verify all requests and dispatch them to staging projects or approve them
|
Verify all requests and dispatch them to staging projects or
|
||||||
|
approve them
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# get all current pending requests
|
# get all current pending requests
|
||||||
@ -320,7 +329,8 @@ class StagingAPI(object):
|
|||||||
self.set_prj_pseudometa(project, data)
|
self.set_prj_pseudometa(project, data)
|
||||||
# FIXME Add sr to group request as well
|
# FIXME Add sr to group request as well
|
||||||
|
|
||||||
def rm_from_prj(self, project, package=None, request_id=None, msg=None, review='accepted'):
|
def rm_from_prj(self, project, package=None, request_id=None,
|
||||||
|
msg=None, review='accepted'):
|
||||||
"""
|
"""
|
||||||
Delete request from the project
|
Delete request from the project
|
||||||
:param project: project to remove from
|
:param project: project to remove from
|
||||||
@ -346,9 +356,11 @@ class StagingAPI(object):
|
|||||||
Creates a package container without any fields in project/package
|
Creates a package container without any fields in project/package
|
||||||
:param project: project to create it
|
:param project: project to create it
|
||||||
:param package: package name
|
:param package: package name
|
||||||
:param disable_build: should the package be created with build flag disabled
|
:param disable_build: should the package be created with build
|
||||||
|
flag disabled
|
||||||
"""
|
"""
|
||||||
dst_meta = '<package name="%s"><title/><description/></package>' % package
|
dst_meta = '<package name="{}"><title/><description/></package>'
|
||||||
|
dst_meta = dst_meta.format(package)
|
||||||
if disable_build:
|
if disable_build:
|
||||||
root = ET.fromstring(dst_meta)
|
root = ET.fromstring(dst_meta)
|
||||||
elm = ET.SubElement(root, 'build')
|
elm = ET.SubElement(root, 'build')
|
||||||
@ -360,10 +372,12 @@ class StagingAPI(object):
|
|||||||
|
|
||||||
def check_one_request(self, request, project):
|
def check_one_request(self, request, project):
|
||||||
"""
|
"""
|
||||||
Check if a staging request is ready to be approved. Reviews for the project
|
Check if a staging request is ready to be approved. Reviews for
|
||||||
are ignored, other open reviews will block the acceptance
|
the project are ignored, other open reviews will block the
|
||||||
|
acceptance
|
||||||
:param project: staging project
|
:param project: staging project
|
||||||
:param request_id: request id to check
|
:param request_id: request id to check
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
f = http_GET(self.makeurl(['request', str(request)]))
|
f = http_GET(self.makeurl(['request', str(request)]))
|
||||||
@ -376,9 +390,10 @@ class StagingAPI(object):
|
|||||||
if state in ['declined', 'superseded', 'revoked']:
|
if state in ['declined', 'superseded', 'revoked']:
|
||||||
return '{}: {}'.format(package, state)
|
return '{}: {}'.format(package, state)
|
||||||
|
|
||||||
# instead of just printing the state of the whole request find out who is
|
# instead of just printing the state of the whole request find
|
||||||
# remaining on the review and print it out, otherwise print out that it is
|
# out who is remaining on the review and print it out,
|
||||||
# ready for approval and waiting on others from GR to be accepted
|
# otherwise print out that it is ready for approval and
|
||||||
|
# waiting on others from GR to be accepted
|
||||||
review_state = root.findall('review')
|
review_state = root.findall('review')
|
||||||
failing_groups = []
|
failing_groups = []
|
||||||
for i in review_state:
|
for i in review_state:
|
||||||
@ -399,10 +414,11 @@ class StagingAPI(object):
|
|||||||
|
|
||||||
def check_project_status(self, project, verbose=False):
|
def check_project_status(self, project, verbose=False):
|
||||||
"""
|
"""
|
||||||
Checks a staging project for acceptance. Checks all open requests for open reviews
|
Checks a staging project for acceptance. Checks all open
|
||||||
and build status
|
requests for open reviews and build status
|
||||||
:param project: project to check
|
:param project: project to check
|
||||||
:return true (ok)/false (empty prj) or list of strings with informations)
|
:return true (ok)/false (empty prj) or list of strings with
|
||||||
|
informations)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Report
|
# Report
|
||||||
@ -412,7 +428,8 @@ class StagingAPI(object):
|
|||||||
requests = self.list_requests_in_prj(project)
|
requests = self.list_requests_in_prj(project)
|
||||||
open_requests = set(requests)
|
open_requests = set(requests)
|
||||||
|
|
||||||
# all tracked requests - some of them might be declined, so we don't see them above
|
# all tracked requests - some of them might be declined, so we
|
||||||
|
# don't see them above
|
||||||
meta = self.get_prj_pseudometa(project)
|
meta = self.get_prj_pseudometa(project)
|
||||||
for req in meta['requests']:
|
for req in meta['requests']:
|
||||||
req = req['id']
|
req = req['id']
|
||||||
@ -489,7 +506,8 @@ class StagingAPI(object):
|
|||||||
# don't look here - we will replace that once we have OBS<->openQA sync
|
# don't look here - we will replace that once we have OBS<->openQA sync
|
||||||
baseurl = 'http://opensuseqa.suse.de/openqa/testresults/openSUSE-Factory-staging'
|
baseurl = 'http://opensuseqa.suse.de/openqa/testresults/openSUSE-Factory-staging'
|
||||||
url = baseurl + '_' + project.split(':')[-1].lower() + '-x86_64-Build'
|
url = baseurl + '_' + project.split(':')[-1].lower() + '-x86_64-Build'
|
||||||
result = re.match('Test-([\d\.]+)-Build(\d+)\.(\d+)-Media.iso', filename)
|
result = re.match('Test-([\d\.]+)-Build(\d+)\.(\d+)-Media.iso',
|
||||||
|
filename)
|
||||||
url += result.group(1)
|
url += result.group(1)
|
||||||
bn = int(result.group(2)) * 100 + int(result.group(3))
|
bn = int(result.group(2)) * 100 + int(result.group(3))
|
||||||
url += '.{}'.format(bn)
|
url += '.{}'.format(bn)
|
||||||
@ -531,7 +549,8 @@ class StagingAPI(object):
|
|||||||
building = False
|
building = False
|
||||||
if results.get('state') not in ('published', 'unpublished') or results.get('dirty') == 'true':
|
if results.get('state') not in ('published', 'unpublished') or results.get('dirty') == 'true':
|
||||||
working.append({
|
working.append({
|
||||||
'path': '{}/{}'.format(results.get('repository'), results.get('arch')),
|
'path': '{}/{}'.format(results.get('repository'),
|
||||||
|
results.get('arch')),
|
||||||
'state': results.get('state')
|
'state': results.get('state')
|
||||||
})
|
})
|
||||||
building = True
|
building = True
|
||||||
@ -543,7 +562,8 @@ class StagingAPI(object):
|
|||||||
broken.append({
|
broken.append({
|
||||||
'pkg': node.get('package'),
|
'pkg': node.get('package'),
|
||||||
'state': result,
|
'state': result,
|
||||||
'path': '{}/{}'.format(results.get('repository'), results.get('arch'))
|
'path': '{}/{}'.format(results.get('repository'),
|
||||||
|
results.get('arch'))
|
||||||
})
|
})
|
||||||
|
|
||||||
# Print the results
|
# Print the results
|
||||||
@ -573,7 +593,8 @@ class StagingAPI(object):
|
|||||||
if len(broken) != 0:
|
if len(broken) != 0:
|
||||||
retval.append('Following packages are broken:')
|
retval.append('Following packages are broken:')
|
||||||
for i in broken:
|
for i in broken:
|
||||||
retval.append(' {} ({}): {}'.format(i['pkg'], i['path'], i['state']))
|
retval.append(' {} ({}): {}'.format(i['pkg'], i['path'],
|
||||||
|
i['state']))
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
@ -599,7 +620,9 @@ class StagingAPI(object):
|
|||||||
tar_pkg = self.delete_to_prj(act[0], project)
|
tar_pkg = self.delete_to_prj(act[0], project)
|
||||||
|
|
||||||
if not tar_pkg:
|
if not tar_pkg:
|
||||||
raise oscerr.WrongArgs('Request {} is not a submit or delete request'.format(request_id))
|
msg = 'Request {} is not a submit or delete request'
|
||||||
|
msg = msg.format(request_id)
|
||||||
|
raise oscerr.WrongArgs(msg)
|
||||||
|
|
||||||
# register the package name
|
# register the package name
|
||||||
self._add_rq_to_prj_pseudometa(project, int(request_id), tar_pkg)
|
self._add_rq_to_prj_pseudometa(project, int(request_id), tar_pkg)
|
||||||
@ -624,7 +647,8 @@ class StagingAPI(object):
|
|||||||
# create build disabled package
|
# create build disabled package
|
||||||
self.create_package_container(project, tar_pkg, disable_build=True)
|
self.create_package_container(project, tar_pkg, disable_build=True)
|
||||||
# now trigger wipebinaries to emulate a delete
|
# now trigger wipebinaries to emulate a delete
|
||||||
url = self.makeurl(['build', project], {'cmd': 'wipe', 'package': tar_pkg})
|
url = self.makeurl(['build', project],
|
||||||
|
{'cmd': 'wipe', 'package': tar_pkg})
|
||||||
http_POST(url)
|
http_POST(url)
|
||||||
|
|
||||||
return tar_pkg
|
return tar_pkg
|
||||||
@ -644,17 +668,21 @@ class StagingAPI(object):
|
|||||||
disable_build = False
|
disable_build = False
|
||||||
if not self.ring_packages.get(tar_pkg):
|
if not self.ring_packages.get(tar_pkg):
|
||||||
disable_build = True
|
disable_build = True
|
||||||
self.create_package_container(project, tar_pkg, disable_build=disable_build)
|
self.create_package_container(project, tar_pkg,
|
||||||
|
disable_build=disable_build)
|
||||||
|
|
||||||
# expand the revision to a md5
|
# expand the revision to a md5
|
||||||
url = self.makeurl(['source', src_prj, src_pkg], {'rev': src_rev, 'expand': 1})
|
url = self.makeurl(['source', src_prj, src_pkg],
|
||||||
|
{'rev': src_rev, 'expand': 1})
|
||||||
f = http_GET(url)
|
f = http_GET(url)
|
||||||
root = ET.parse(f).getroot()
|
root = ET.parse(f).getroot()
|
||||||
src_rev = root.attrib['srcmd5']
|
src_rev = root.attrib['srcmd5']
|
||||||
src_vrev = root.attrib.get('vrev')
|
src_vrev = root.attrib.get('vrev')
|
||||||
|
|
||||||
# link stuff - not using linkpac because linkpac copies meta from source
|
# link stuff - not using linkpac because linkpac copies meta
|
||||||
root = ET.Element('link', package=src_pkg, project=src_prj, rev=src_rev)
|
# from source
|
||||||
|
root = ET.Element('link', package=src_pkg, project=src_prj,
|
||||||
|
rev=src_rev)
|
||||||
if src_vrev:
|
if src_vrev:
|
||||||
root.attrib['vrev'] = src_vrev
|
root.attrib['vrev'] = src_vrev
|
||||||
url = self.makeurl(['source', project, tar_pkg, '_link'])
|
url = self.makeurl(['source', project, tar_pkg, '_link'])
|
||||||
@ -698,7 +726,8 @@ class StagingAPI(object):
|
|||||||
if by_project:
|
if by_project:
|
||||||
query['by_project'] = by_project
|
query['by_project'] = by_project
|
||||||
if not msg:
|
if not msg:
|
||||||
msg = 'Being evaluated by staging project "{}"'.format(by_project)
|
msg = 'Being evaluated by staging project "{}"'
|
||||||
|
msg = msg.format(by_project)
|
||||||
if by_group:
|
if by_group:
|
||||||
query['by_group'] = by_group
|
query['by_group'] = by_group
|
||||||
if not msg:
|
if not msg:
|
||||||
@ -723,7 +752,8 @@ class StagingAPI(object):
|
|||||||
if i.by_project == project and i.state == 'new':
|
if i.by_project == project and i.state == 'new':
|
||||||
cont = True
|
cont = True
|
||||||
if cont:
|
if cont:
|
||||||
msg = 'Reviewed by staging project "{}" with result: "{}"'.format(project, state)
|
msg = 'Reviewed by staging project "{}" with result: "{}"'
|
||||||
|
msg = msg.format(project, state)
|
||||||
self.change_review_state(request_id, state, by_project=project,
|
self.change_review_state(request_id, state, by_project=project,
|
||||||
message=msg)
|
message=msg)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user